Bug 1880558: Allow PnP windows to enter macOS native fullscreen. r=pip-reviewers...
[gecko.git] / dom / html / HTMLInputElement.cpp
blobe52ceec22475eed7dd50ed2a75ee61384f091b99
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/HTMLInputElement.h"
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/AsyncEventDispatcher.h"
11 #include "mozilla/BasePrincipal.h"
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/Components.h"
14 #include "mozilla/dom/AutocompleteInfoBinding.h"
15 #include "mozilla/dom/BlobImpl.h"
16 #include "mozilla/dom/Directory.h"
17 #include "mozilla/dom/DocumentOrShadowRoot.h"
18 #include "mozilla/dom/ElementBinding.h"
19 #include "mozilla/dom/FileSystemUtils.h"
20 #include "mozilla/dom/FormData.h"
21 #include "mozilla/dom/GetFilesHelper.h"
22 #include "mozilla/dom/NumericInputTypes.h"
23 #include "mozilla/dom/WindowContext.h"
24 #include "mozilla/dom/InputType.h"
25 #include "mozilla/dom/UserActivation.h"
26 #include "mozilla/dom/MutationEventBinding.h"
27 #include "mozilla/dom/WheelEventBinding.h"
28 #include "mozilla/dom/WindowGlobalChild.h"
29 #include "mozilla/EventStateManager.h"
30 #include "mozilla/PresShell.h"
31 #include "mozilla/StaticPrefs_dom.h"
32 #include "mozilla/StaticPrefs_signon.h"
33 #include "mozilla/TextUtils.h"
34 #include "mozilla/Try.h"
35 #include "nsAttrValueInlines.h"
36 #include "nsCRTGlue.h"
37 #include "nsIFilePicker.h"
38 #include "nsNetUtil.h"
39 #include "nsQueryObject.h"
41 #include "HTMLDataListElement.h"
42 #include "HTMLFormSubmissionConstants.h"
43 #include "mozilla/Telemetry.h"
44 #include "nsBaseCommandController.h"
45 #include "nsIStringBundle.h"
46 #include "nsFocusManager.h"
47 #include "nsColorControlFrame.h"
48 #include "nsNumberControlFrame.h"
49 #include "nsSearchControlFrame.h"
50 #include "nsPIDOMWindow.h"
51 #include "nsRepeatService.h"
52 #include "nsContentCID.h"
53 #include "mozilla/dom/ProgressEvent.h"
54 #include "nsGkAtoms.h"
55 #include "nsStyleConsts.h"
56 #include "nsPresContext.h"
57 #include "nsIFormControl.h"
58 #include "mozilla/dom/Document.h"
59 #include "mozilla/dom/HTMLDataListElement.h"
60 #include "mozilla/dom/HTMLOptionElement.h"
61 #include "nsIFormControlFrame.h"
62 #include "nsITextControlFrame.h"
63 #include "nsIFrame.h"
64 #include "nsRangeFrame.h"
65 #include "nsError.h"
66 #include "nsIEditor.h"
67 #include "nsIPromptCollection.h"
69 #include "mozilla/PresState.h"
70 #include "nsLinebreakConverter.h" //to strip out carriage returns
71 #include "nsReadableUtils.h"
72 #include "nsUnicharUtils.h"
73 #include "nsLayoutUtils.h"
74 #include "nsVariant.h"
76 #include "mozilla/ContentEvents.h"
77 #include "mozilla/EventDispatcher.h"
78 #include "mozilla/MappedDeclarationsBuilder.h"
79 #include "mozilla/InternalMutationEvent.h"
80 #include "mozilla/TextControlState.h"
81 #include "mozilla/TextEditor.h"
82 #include "mozilla/TextEvents.h"
83 #include "mozilla/TouchEvents.h"
85 #include <algorithm>
87 // input type=radio
88 #include "mozilla/dom/RadioGroupContainer.h"
89 #include "nsIRadioVisitor.h"
90 #include "nsRadioVisitor.h"
92 // input type=file
93 #include "mozilla/dom/FileSystemEntry.h"
94 #include "mozilla/dom/FileSystem.h"
95 #include "mozilla/dom/File.h"
96 #include "mozilla/dom/FileList.h"
97 #include "nsIFile.h"
98 #include "nsDirectoryServiceDefs.h"
99 #include "nsIContentPrefService2.h"
100 #include "nsIMIMEService.h"
101 #include "nsIObserverService.h"
103 // input type=image
104 #include "nsImageLoadingContent.h"
105 #include "imgRequestProxy.h"
107 #include "mozAutoDocUpdate.h"
108 #include "nsContentCreatorFunctions.h"
109 #include "nsContentUtils.h"
110 #include "mozilla/dom/DirectionalityUtils.h"
112 #include "mozilla/LookAndFeel.h"
113 #include "mozilla/Preferences.h"
114 #include "mozilla/MathAlgorithms.h"
116 #include <limits>
118 #include "nsIColorPicker.h"
119 #include "nsIStringEnumerator.h"
120 #include "HTMLSplitOnSpacesTokenizer.h"
121 #include "nsIMIMEInfo.h"
122 #include "nsFrameSelection.h"
123 #include "nsXULControllers.h"
125 // input type=date
126 #include "js/Date.h"
128 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
130 // XXX align=left, hspace, vspace, border? other nav4 attrs
132 namespace mozilla::dom {
134 // First bits are needed for the control type.
135 #define NS_OUTER_ACTIVATE_EVENT (1 << 9)
136 #define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
137 // (1 << 11 is unused)
138 #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
139 #define NS_PRE_HANDLE_BLUR_EVENT (1 << 13)
140 #define NS_IN_SUBMIT_CLICK (1 << 15)
141 #define NS_CONTROL_TYPE(bits) \
142 ((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \
143 NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \
144 NS_IN_SUBMIT_CLICK))
146 // whether textfields should be selected once focused:
147 // -1: no, 1: yes, 0: uninitialized
148 static int32_t gSelectTextFieldOnFocus;
149 UploadLastDir* HTMLInputElement::gUploadLastDir;
151 static const nsAttrValue::EnumTable kInputTypeTable[] = {
152 {"button", FormControlType::InputButton},
153 {"checkbox", FormControlType::InputCheckbox},
154 {"color", FormControlType::InputColor},
155 {"date", FormControlType::InputDate},
156 {"datetime-local", FormControlType::InputDatetimeLocal},
157 {"email", FormControlType::InputEmail},
158 {"file", FormControlType::InputFile},
159 {"hidden", FormControlType::InputHidden},
160 {"reset", FormControlType::InputReset},
161 {"image", FormControlType::InputImage},
162 {"month", FormControlType::InputMonth},
163 {"number", FormControlType::InputNumber},
164 {"password", FormControlType::InputPassword},
165 {"radio", FormControlType::InputRadio},
166 {"range", FormControlType::InputRange},
167 {"search", FormControlType::InputSearch},
168 {"submit", FormControlType::InputSubmit},
169 {"tel", FormControlType::InputTel},
170 {"time", FormControlType::InputTime},
171 {"url", FormControlType::InputUrl},
172 {"week", FormControlType::InputWeek},
173 // "text" must be last for ParseAttribute to work right. If you add things
174 // before it, please update kInputDefaultType.
175 {"text", FormControlType::InputText},
176 {nullptr, 0}};
178 // Default type is 'text'.
179 static const nsAttrValue::EnumTable* kInputDefaultType =
180 &kInputTypeTable[ArrayLength(kInputTypeTable) - 2];
182 static const nsAttrValue::EnumTable kCaptureTable[] = {
183 {"user", nsIFilePicker::captureUser},
184 {"environment", nsIFilePicker::captureEnv},
185 {"", nsIFilePicker::captureDefault},
186 {nullptr, nsIFilePicker::captureNone}};
188 static const nsAttrValue::EnumTable* kCaptureDefault = &kCaptureTable[2];
190 using namespace blink;
192 constexpr Decimal HTMLInputElement::kStepScaleFactorDate(86400000_d);
193 constexpr Decimal HTMLInputElement::kStepScaleFactorNumberRange(1_d);
194 constexpr Decimal HTMLInputElement::kStepScaleFactorTime(1000_d);
195 constexpr Decimal HTMLInputElement::kStepScaleFactorMonth(1_d);
196 constexpr Decimal HTMLInputElement::kStepScaleFactorWeek(7 * 86400000_d);
197 constexpr Decimal HTMLInputElement::kDefaultStepBase(0_d);
198 constexpr Decimal HTMLInputElement::kDefaultStepBaseWeek(-259200000_d);
199 constexpr Decimal HTMLInputElement::kDefaultStep(1_d);
200 constexpr Decimal HTMLInputElement::kDefaultStepTime(60_d);
201 constexpr Decimal HTMLInputElement::kStepAny(0_d);
203 const double HTMLInputElement::kMinimumYear = 1;
204 const double HTMLInputElement::kMaximumYear = 275760;
205 const double HTMLInputElement::kMaximumWeekInMaximumYear = 37;
206 const double HTMLInputElement::kMaximumDayInMaximumYear = 13;
207 const double HTMLInputElement::kMaximumMonthInMaximumYear = 9;
208 const double HTMLInputElement::kMaximumWeekInYear = 53;
209 const double HTMLInputElement::kMsPerDay = 24 * 60 * 60 * 1000;
211 // An helper class for the dispatching of the 'change' event.
212 // This class is used when the FilePicker finished its task (or when files and
213 // directories are set by some chrome/test only method).
214 // The task of this class is to postpone the dispatching of 'change' and 'input'
215 // events at the end of the exploration of the directories.
216 class DispatchChangeEventCallback final : public GetFilesCallback {
217 public:
218 explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
219 : mInputElement(aInputElement) {
220 MOZ_ASSERT(aInputElement);
223 virtual void Callback(
224 nsresult aStatus,
225 const FallibleTArray<RefPtr<BlobImpl>>& aBlobImpls) override {
226 if (!mInputElement->GetOwnerGlobal()) {
227 return;
230 nsTArray<OwningFileOrDirectory> array;
231 for (uint32_t i = 0; i < aBlobImpls.Length(); ++i) {
232 OwningFileOrDirectory* element = array.AppendElement();
233 RefPtr<File> file =
234 File::Create(mInputElement->GetOwnerGlobal(), aBlobImpls[i]);
235 if (NS_WARN_IF(!file)) {
236 return;
239 element->SetAsFile() = file;
242 mInputElement->SetFilesOrDirectories(array, true);
243 Unused << NS_WARN_IF(NS_FAILED(DispatchEvents()));
246 MOZ_CAN_RUN_SCRIPT_BOUNDARY
247 nsresult DispatchEvents() {
248 RefPtr<HTMLInputElement> inputElement(mInputElement);
249 nsresult rv = nsContentUtils::DispatchInputEvent(inputElement);
250 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch input event");
251 mInputElement->SetUserInteracted(true);
252 rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
253 mInputElement, u"change"_ns,
254 CanBubble::eYes, Cancelable::eNo);
256 return rv;
259 private:
260 RefPtr<HTMLInputElement> mInputElement;
263 struct HTMLInputElement::FileData {
265 * The value of the input if it is a file input. This is the list of files or
266 * directories DOM objects used when uploading a file. It is vital that this
267 * is kept separate from mValue so that it won't be possible to 'leak' the
268 * value from a text-input to a file-input. Additionally, the logic for this
269 * value is kept as simple as possible to avoid accidental errors where the
270 * wrong filename is used. Therefor the list of filenames is always owned by
271 * this member, never by the frame. Whenever the frame wants to change the
272 * filename it has to call SetFilesOrDirectories to update this member.
274 nsTArray<OwningFileOrDirectory> mFilesOrDirectories;
276 RefPtr<GetFilesHelper> mGetFilesRecursiveHelper;
277 RefPtr<GetFilesHelper> mGetFilesNonRecursiveHelper;
280 * Hack for bug 1086684: Stash the .value when we're a file picker.
282 nsString mFirstFilePath;
284 RefPtr<FileList> mFileList;
285 Sequence<RefPtr<FileSystemEntry>> mEntries;
287 nsString mStaticDocFileList;
289 void ClearGetFilesHelpers() {
290 if (mGetFilesRecursiveHelper) {
291 mGetFilesRecursiveHelper->Unlink();
292 mGetFilesRecursiveHelper = nullptr;
295 if (mGetFilesNonRecursiveHelper) {
296 mGetFilesNonRecursiveHelper->Unlink();
297 mGetFilesNonRecursiveHelper = nullptr;
301 // Cycle Collection support.
302 void Traverse(nsCycleCollectionTraversalCallback& cb) {
303 FileData* tmp = this;
304 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
305 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
306 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries)
307 if (mGetFilesRecursiveHelper) {
308 mGetFilesRecursiveHelper->Traverse(cb);
311 if (mGetFilesNonRecursiveHelper) {
312 mGetFilesNonRecursiveHelper->Traverse(cb);
316 void Unlink() {
317 FileData* tmp = this;
318 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
319 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
320 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries)
321 ClearGetFilesHelpers();
325 HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
326 HTMLInputElement* aInput, nsIFilePicker* aFilePicker)
327 : mFilePicker(aFilePicker), mInput(aInput) {}
329 NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2)
331 NS_IMETHODIMP
332 UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason) {
333 nsCOMPtr<nsIFile> localFile;
334 nsAutoString prefStr;
336 if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) {
337 Preferences::GetString("dom.input.fallbackUploadDir", prefStr);
340 if (prefStr.IsEmpty() && mResult) {
341 nsCOMPtr<nsIVariant> pref;
342 mResult->GetValue(getter_AddRefs(pref));
343 pref->GetAsAString(prefStr);
346 if (!prefStr.IsEmpty()) {
347 localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
348 if (localFile && NS_WARN_IF(NS_FAILED(localFile->InitWithPath(prefStr)))) {
349 localFile = nullptr;
353 if (localFile) {
354 mFilePicker->SetDisplayDirectory(localFile);
355 } else {
356 // If no custom directory was set through the pref, default to
357 // "desktop" directory for each platform.
358 mFilePicker->SetDisplaySpecialDirectory(
359 NS_LITERAL_STRING_FROM_CSTRING(NS_OS_DESKTOP_DIR));
362 mFilePicker->Open(mFpCallback);
363 return NS_OK;
366 NS_IMETHODIMP
367 UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref* pref) {
368 mResult = pref;
369 return NS_OK;
372 NS_IMETHODIMP
373 UploadLastDir::ContentPrefCallback::HandleError(nsresult error) {
374 // HandleCompletion is always called (even with HandleError was called),
375 // so we don't need to do anything special here.
376 return NS_OK;
379 namespace {
382 * This may return nullptr if the DOM File's implementation of
383 * File::mozFullPathInternal does not successfully return a non-empty
384 * string that is a valid path. This can happen on Firefox OS, for example,
385 * where the file picker can create Blobs.
387 static already_AddRefed<nsIFile> LastUsedDirectory(
388 const OwningFileOrDirectory& aData) {
389 if (aData.IsFile()) {
390 nsAutoString path;
391 ErrorResult error;
392 aData.GetAsFile()->GetMozFullPathInternal(path, error);
393 if (error.Failed() || path.IsEmpty()) {
394 error.SuppressException();
395 return nullptr;
398 nsCOMPtr<nsIFile> localFile;
399 nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(localFile));
400 if (NS_WARN_IF(NS_FAILED(rv))) {
401 return nullptr;
404 nsCOMPtr<nsIFile> parentFile;
405 rv = localFile->GetParent(getter_AddRefs(parentFile));
406 if (NS_WARN_IF(NS_FAILED(rv))) {
407 return nullptr;
410 return parentFile.forget();
413 MOZ_ASSERT(aData.IsDirectory());
415 nsCOMPtr<nsIFile> localFile = aData.GetAsDirectory()->GetInternalNsIFile();
416 MOZ_ASSERT(localFile);
418 return localFile.forget();
421 void GetDOMFileOrDirectoryName(const OwningFileOrDirectory& aData,
422 nsAString& aName) {
423 if (aData.IsFile()) {
424 aData.GetAsFile()->GetName(aName);
425 } else {
426 MOZ_ASSERT(aData.IsDirectory());
427 ErrorResult rv;
428 aData.GetAsDirectory()->GetName(aName, rv);
429 if (NS_WARN_IF(rv.Failed())) {
430 rv.SuppressException();
435 void GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
436 nsAString& aPath, ErrorResult& aRv) {
437 if (aData.IsFile()) {
438 aData.GetAsFile()->GetMozFullPathInternal(aPath, aRv);
439 } else {
440 MOZ_ASSERT(aData.IsDirectory());
441 aData.GetAsDirectory()->GetFullRealPath(aPath);
445 } // namespace
447 NS_IMETHODIMP
448 HTMLInputElement::nsFilePickerShownCallback::Done(
449 nsIFilePicker::ResultCode aResult) {
450 mInput->PickerClosed();
452 if (aResult == nsIFilePicker::returnCancel) {
453 RefPtr<HTMLInputElement> inputElement(mInput);
454 return nsContentUtils::DispatchTrustedEvent(
455 inputElement->OwnerDoc(), inputElement, u"cancel"_ns, CanBubble::eYes,
456 Cancelable::eNo);
459 mInput->OwnerDoc()->NotifyUserGestureActivation();
461 nsIFilePicker::Mode mode;
462 mFilePicker->GetMode(&mode);
464 // Collect new selected filenames
465 nsTArray<OwningFileOrDirectory> newFilesOrDirectories;
466 if (mode == nsIFilePicker::modeOpenMultiple) {
467 nsCOMPtr<nsISimpleEnumerator> iter;
468 nsresult rv =
469 mFilePicker->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter));
470 NS_ENSURE_SUCCESS(rv, rv);
472 if (!iter) {
473 return NS_OK;
476 nsCOMPtr<nsISupports> tmp;
477 bool hasMore = true;
479 while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
480 iter->GetNext(getter_AddRefs(tmp));
481 RefPtr<Blob> domBlob = do_QueryObject(tmp);
482 MOZ_ASSERT(domBlob,
483 "Null file object from FilePicker's file enumerator?");
484 if (!domBlob) {
485 continue;
488 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
489 element->SetAsFile() = domBlob->ToFile();
491 } else {
492 MOZ_ASSERT(mode == nsIFilePicker::modeOpen ||
493 mode == nsIFilePicker::modeGetFolder);
494 nsCOMPtr<nsISupports> tmp;
495 nsresult rv = mFilePicker->GetDomFileOrDirectory(getter_AddRefs(tmp));
496 NS_ENSURE_SUCCESS(rv, rv);
498 // Show a prompt to get user confirmation before allowing folder access.
499 // This is to prevent sites from tricking the user into uploading files.
500 // See Bug 1338637.
501 if (mode == nsIFilePicker::modeGetFolder) {
502 nsCOMPtr<nsIPromptCollection> prompter =
503 do_GetService("@mozilla.org/embedcomp/prompt-collection;1");
504 if (!prompter) {
505 return NS_ERROR_NOT_AVAILABLE;
508 bool confirmed = false;
509 BrowsingContext* bc = mInput->OwnerDoc()->GetBrowsingContext();
511 // Get directory name
512 RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
513 nsAutoString directoryName;
514 ErrorResult error;
515 directory->GetName(directoryName, error);
516 if (NS_WARN_IF(error.Failed())) {
517 return error.StealNSResult();
520 rv = prompter->ConfirmFolderUpload(bc, directoryName, &confirmed);
521 NS_ENSURE_SUCCESS(rv, rv);
522 if (!confirmed) {
523 // User aborted upload
524 return NS_OK;
528 RefPtr<Blob> blob = do_QueryObject(tmp);
529 if (blob) {
530 RefPtr<File> file = blob->ToFile();
531 MOZ_ASSERT(file);
533 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
534 element->SetAsFile() = file;
535 } else if (tmp) {
536 RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
537 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
538 element->SetAsDirectory() = directory;
542 if (newFilesOrDirectories.IsEmpty()) {
543 return NS_OK;
546 // Store the last used directory using the content pref service:
547 nsCOMPtr<nsIFile> lastUsedDir = LastUsedDirectory(newFilesOrDirectories[0]);
549 if (lastUsedDir) {
550 HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(mInput->OwnerDoc(),
551 lastUsedDir);
554 // The text control frame (if there is one) isn't going to send a change
555 // event because it will think this is done by a script.
556 // So, we can safely send one by ourself.
557 mInput->SetFilesOrDirectories(newFilesOrDirectories, true);
559 // mInput(HTMLInputElement) has no scriptGlobalObject, don't create
560 // DispatchChangeEventCallback
561 if (!mInput->GetOwnerGlobal()) {
562 return NS_OK;
564 RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
565 new DispatchChangeEventCallback(mInput);
567 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
568 mInput->HasAttr(nsGkAtoms::webkitdirectory)) {
569 ErrorResult error;
570 GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
571 if (NS_WARN_IF(error.Failed())) {
572 return error.StealNSResult();
575 helper->AddCallback(dispatchChangeEventCallback);
576 return NS_OK;
579 return dispatchChangeEventCallback->DispatchEvents();
582 NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback,
583 nsIFilePickerShownCallback)
585 class nsColorPickerShownCallback final : public nsIColorPickerShownCallback {
586 ~nsColorPickerShownCallback() = default;
588 public:
589 nsColorPickerShownCallback(HTMLInputElement* aInput,
590 nsIColorPicker* aColorPicker)
591 : mInput(aInput), mColorPicker(aColorPicker), mValueChanged(false) {}
593 NS_DECL_ISUPPORTS
595 MOZ_CAN_RUN_SCRIPT_BOUNDARY
596 NS_IMETHOD Update(const nsAString& aColor) override;
597 MOZ_CAN_RUN_SCRIPT_BOUNDARY
598 NS_IMETHOD Done(const nsAString& aColor) override;
600 private:
602 * Updates the internals of the object using aColor as the new value.
603 * If aTrustedUpdate is true, it will consider that aColor is a new value.
604 * Otherwise, it will check that aColor is different from the current value.
606 MOZ_CAN_RUN_SCRIPT
607 nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
609 RefPtr<HTMLInputElement> mInput;
610 nsCOMPtr<nsIColorPicker> mColorPicker;
611 bool mValueChanged;
614 nsresult nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
615 bool aTrustedUpdate) {
616 bool valueChanged = false;
617 nsAutoString oldValue;
618 if (aTrustedUpdate) {
619 mInput->OwnerDoc()->NotifyUserGestureActivation();
620 valueChanged = true;
621 } else {
622 mInput->GetValue(oldValue, CallerType::System);
625 mInput->SetValue(aColor, CallerType::System, IgnoreErrors());
627 if (!aTrustedUpdate) {
628 nsAutoString newValue;
629 mInput->GetValue(newValue, CallerType::System);
630 if (!oldValue.Equals(newValue)) {
631 valueChanged = true;
635 if (!valueChanged) {
636 return NS_OK;
639 mValueChanged = true;
640 RefPtr<HTMLInputElement> input(mInput);
641 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(input);
642 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
643 "Failed to dispatch input event");
644 return NS_OK;
647 NS_IMETHODIMP
648 nsColorPickerShownCallback::Update(const nsAString& aColor) {
649 return UpdateInternal(aColor, true);
652 NS_IMETHODIMP
653 nsColorPickerShownCallback::Done(const nsAString& aColor) {
655 * When Done() is called, we might be at the end of a serie of Update() calls
656 * in which case mValueChanged is set to true and a change event will have to
657 * be fired but we might also be in a one shot Done() call situation in which
658 * case we should fire a change event iif the value actually changed.
659 * UpdateInternal(bool) is taking care of that logic for us.
661 nsresult rv = NS_OK;
663 mInput->PickerClosed();
665 if (!aColor.IsEmpty()) {
666 UpdateInternal(aColor, false);
669 if (mValueChanged) {
670 mInput->SetUserInteracted(true);
671 rv = nsContentUtils::DispatchTrustedEvent(
672 mInput->OwnerDoc(), static_cast<Element*>(mInput.get()), u"change"_ns,
673 CanBubble::eYes, Cancelable::eNo);
676 return rv;
679 NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback)
681 static bool IsPopupBlocked(Document* aDoc) {
682 if (aDoc->ConsumeTransientUserGestureActivation()) {
683 return false;
686 WindowContext* wc = aDoc->GetWindowContext();
687 if (wc && wc->CanShowPopup()) {
688 return false;
691 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, aDoc,
692 nsContentUtils::eDOM_PROPERTIES,
693 "InputPickerBlockedNoUserActivation");
694 return true;
697 nsTArray<nsString> HTMLInputElement::GetColorsFromList() {
698 RefPtr<HTMLDataListElement> dataList = GetList();
699 if (!dataList) {
700 return {};
703 nsTArray<nsString> colors;
705 RefPtr<nsContentList> options = dataList->Options();
706 uint32_t length = options->Length(true);
707 for (uint32_t i = 0; i < length; ++i) {
708 auto* option = HTMLOptionElement::FromNodeOrNull(options->Item(i, false));
709 if (!option) {
710 continue;
713 nsString value;
714 option->GetValue(value);
715 if (IsValidSimpleColor(value)) {
716 ToLowerCase(value);
717 colors.AppendElement(value);
721 return colors;
724 nsresult HTMLInputElement::InitColorPicker() {
725 MOZ_ASSERT(IsMutable());
727 if (mPickerRunning) {
728 NS_WARNING("Just one nsIColorPicker is allowed");
729 return NS_ERROR_FAILURE;
732 nsCOMPtr<Document> doc = OwnerDoc();
734 nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
735 if (!win) {
736 return NS_ERROR_FAILURE;
739 if (IsPopupBlocked(doc)) {
740 return NS_OK;
743 // Get Loc title
744 nsAutoString title;
745 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
746 "ColorPicker", title);
748 nsCOMPtr<nsIColorPicker> colorPicker =
749 do_CreateInstance("@mozilla.org/colorpicker;1");
750 if (!colorPicker) {
751 return NS_ERROR_FAILURE;
754 nsAutoString initialValue;
755 GetNonFileValueInternal(initialValue);
756 nsTArray<nsString> colors = GetColorsFromList();
757 nsresult rv = colorPicker->Init(win, title, initialValue, colors);
758 NS_ENSURE_SUCCESS(rv, rv);
760 nsCOMPtr<nsIColorPickerShownCallback> callback =
761 new nsColorPickerShownCallback(this, colorPicker);
763 rv = colorPicker->Open(callback);
764 if (NS_SUCCEEDED(rv)) {
765 mPickerRunning = true;
768 return rv;
771 nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
772 MOZ_ASSERT(IsMutable());
774 if (mPickerRunning) {
775 NS_WARNING("Just one nsIFilePicker is allowed");
776 return NS_ERROR_FAILURE;
779 // Get parent nsPIDOMWindow object.
780 nsCOMPtr<Document> doc = OwnerDoc();
782 nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
783 if (!win) {
784 return NS_ERROR_FAILURE;
787 if (IsPopupBlocked(doc)) {
788 return NS_OK;
791 // Get Loc title
792 nsAutoString title;
793 nsAutoString okButtonLabel;
794 if (aType == FILE_PICKER_DIRECTORY) {
795 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
796 "DirectoryUpload", OwnerDoc(),
797 title);
799 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
800 "DirectoryPickerOkButtonLabel",
801 OwnerDoc(), okButtonLabel);
802 } else {
803 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
804 "FileUpload", OwnerDoc(), title);
807 nsCOMPtr<nsIFilePicker> filePicker =
808 do_CreateInstance("@mozilla.org/filepicker;1");
809 if (!filePicker) return NS_ERROR_FAILURE;
811 nsIFilePicker::Mode mode;
813 if (aType == FILE_PICKER_DIRECTORY) {
814 mode = nsIFilePicker::modeGetFolder;
815 } else if (HasAttr(nsGkAtoms::multiple)) {
816 mode = nsIFilePicker::modeOpenMultiple;
817 } else {
818 mode = nsIFilePicker::modeOpen;
821 nsresult rv =
822 filePicker->Init(win, title, mode, OwnerDoc()->GetBrowsingContext());
823 NS_ENSURE_SUCCESS(rv, rv);
825 if (!okButtonLabel.IsEmpty()) {
826 filePicker->SetOkButtonLabel(okButtonLabel);
829 // Native directory pickers ignore file type filters, so we don't spend
830 // cycles adding them for FILE_PICKER_DIRECTORY.
831 if (HasAttr(nsGkAtoms::accept) && aType != FILE_PICKER_DIRECTORY) {
832 SetFilePickerFiltersFromAccept(filePicker);
834 if (StaticPrefs::dom_capture_enabled()) {
835 if (const nsAttrValue* captureVal = GetParsedAttr(nsGkAtoms::capture)) {
836 filePicker->SetCapture(static_cast<nsIFilePicker::CaptureTarget>(
837 captureVal->GetEnumValue()));
840 } else {
841 filePicker->AppendFilters(nsIFilePicker::filterAll);
844 // Set default directory and filename
845 nsAutoString defaultName;
847 const nsTArray<OwningFileOrDirectory>& oldFiles =
848 GetFilesOrDirectoriesInternal();
850 nsCOMPtr<nsIFilePickerShownCallback> callback =
851 new HTMLInputElement::nsFilePickerShownCallback(this, filePicker);
853 if (!oldFiles.IsEmpty() && aType != FILE_PICKER_DIRECTORY) {
854 nsAutoString path;
856 nsCOMPtr<nsIFile> parentFile = LastUsedDirectory(oldFiles[0]);
857 if (parentFile) {
858 filePicker->SetDisplayDirectory(parentFile);
861 // Unfortunately nsIFilePicker doesn't allow multiple files to be
862 // default-selected, so only select something by default if exactly
863 // one file was selected before.
864 if (oldFiles.Length() == 1) {
865 nsAutoString leafName;
866 GetDOMFileOrDirectoryName(oldFiles[0], leafName);
868 if (!leafName.IsEmpty()) {
869 filePicker->SetDefaultString(leafName);
873 rv = filePicker->Open(callback);
874 if (NS_SUCCEEDED(rv)) {
875 mPickerRunning = true;
878 return rv;
881 HTMLInputElement::gUploadLastDir->FetchDirectoryAndDisplayPicker(
882 doc, filePicker, callback);
883 mPickerRunning = true;
884 return NS_OK;
887 #define CPS_PREF_NAME u"browser.upload.lastDir"_ns
889 NS_IMPL_ISUPPORTS(UploadLastDir, nsIObserver, nsISupportsWeakReference)
891 void HTMLInputElement::InitUploadLastDir() {
892 gUploadLastDir = new UploadLastDir();
893 NS_ADDREF(gUploadLastDir);
895 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
896 if (observerService && gUploadLastDir) {
897 observerService->AddObserver(gUploadLastDir,
898 "browser:purge-session-history", true);
902 void HTMLInputElement::DestroyUploadLastDir() { NS_IF_RELEASE(gUploadLastDir); }
904 nsresult UploadLastDir::FetchDirectoryAndDisplayPicker(
905 Document* aDoc, nsIFilePicker* aFilePicker,
906 nsIFilePickerShownCallback* aFpCallback) {
907 MOZ_ASSERT(aDoc, "aDoc is null");
908 MOZ_ASSERT(aFilePicker, "aFilePicker is null");
909 MOZ_ASSERT(aFpCallback, "aFpCallback is null");
911 nsIURI* docURI = aDoc->GetDocumentURI();
912 MOZ_ASSERT(docURI, "docURI is null");
914 nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
915 nsCOMPtr<nsIContentPrefCallback2> prefCallback =
916 new UploadLastDir::ContentPrefCallback(aFilePicker, aFpCallback);
918 // Attempt to get the CPS, if it's not present we'll fallback to use the
919 // Desktop folder
920 nsCOMPtr<nsIContentPrefService2> contentPrefService =
921 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
922 if (!contentPrefService) {
923 prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
924 return NS_OK;
927 nsAutoCString cstrSpec;
928 docURI->GetSpec(cstrSpec);
929 NS_ConvertUTF8toUTF16 spec(cstrSpec);
931 contentPrefService->GetByDomainAndName(spec, CPS_PREF_NAME, loadContext,
932 prefCallback);
933 return NS_OK;
936 nsresult UploadLastDir::StoreLastUsedDirectory(Document* aDoc, nsIFile* aDir) {
937 MOZ_ASSERT(aDoc, "aDoc is null");
938 if (!aDir) {
939 return NS_OK;
942 nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
943 MOZ_ASSERT(docURI, "docURI is null");
945 // Attempt to get the CPS, if it's not present we'll just return
946 nsCOMPtr<nsIContentPrefService2> contentPrefService =
947 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
948 if (!contentPrefService) return NS_ERROR_NOT_AVAILABLE;
950 nsAutoCString cstrSpec;
951 docURI->GetSpec(cstrSpec);
952 NS_ConvertUTF8toUTF16 spec(cstrSpec);
954 // Find the parent of aFile, and store it
955 nsString unicodePath;
956 aDir->GetPath(unicodePath);
957 if (unicodePath.IsEmpty()) // nothing to do
958 return NS_OK;
959 RefPtr<nsVariantCC> prefValue = new nsVariantCC();
960 prefValue->SetAsAString(unicodePath);
962 // Use the document's current load context to ensure that the content pref
963 // service doesn't persistently store this directory for this domain if the
964 // user is using private browsing:
965 nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
966 return contentPrefService->Set(spec, CPS_PREF_NAME, prefValue, loadContext,
967 nullptr);
970 NS_IMETHODIMP
971 UploadLastDir::Observe(nsISupports* aSubject, char const* aTopic,
972 char16_t const* aData) {
973 if (strcmp(aTopic, "browser:purge-session-history") == 0) {
974 nsCOMPtr<nsIContentPrefService2> contentPrefService =
975 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
976 if (contentPrefService)
977 contentPrefService->RemoveByName(CPS_PREF_NAME, nullptr, nullptr);
979 return NS_OK;
982 #ifdef ACCESSIBILITY
983 // Helper method
984 static nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
985 EventMessage aEventMessage);
986 #endif
989 // construction, destruction
992 HTMLInputElement::HTMLInputElement(already_AddRefed<dom::NodeInfo>&& aNodeInfo,
993 FromParser aFromParser, FromClone aFromClone)
994 : TextControlElement(std::move(aNodeInfo), aFromParser,
995 FormControlType(kInputDefaultType->value)),
996 mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown),
997 mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown),
998 mDisabledChanged(false),
999 mValueChanged(false),
1000 mUserInteracted(false),
1001 mLastValueChangeWasInteractive(false),
1002 mCheckedChanged(false),
1003 mChecked(false),
1004 mHandlingSelectEvent(false),
1005 mShouldInitChecked(false),
1006 mDoneCreating(aFromParser == NOT_FROM_PARSER &&
1007 aFromClone == FromClone::No),
1008 mInInternalActivate(false),
1009 mCheckedIsToggled(false),
1010 mIndeterminate(false),
1011 mInhibitRestoration(aFromParser & FROM_PARSER_FRAGMENT),
1012 mHasRange(false),
1013 mIsDraggingRange(false),
1014 mNumberControlSpinnerIsSpinning(false),
1015 mNumberControlSpinnerSpinsUp(false),
1016 mPickerRunning(false),
1017 mIsPreviewEnabled(false),
1018 mHasBeenTypePassword(false),
1019 mHasPatternAttribute(false),
1020 mRadioGroupContainer(nullptr) {
1021 // If size is above 512, mozjemalloc allocates 1kB, see
1022 // memory/build/mozjemalloc.cpp
1023 static_assert(sizeof(HTMLInputElement) <= 512,
1024 "Keep the size of HTMLInputElement under 512 to avoid "
1025 "performance regression!");
1027 // We are in a type=text but we create TextControlState lazily.
1028 mInputData.mState = nullptr;
1030 void* memory = mInputTypeMem;
1031 mInputType = InputType::Create(this, mType, memory);
1033 if (!gUploadLastDir) HTMLInputElement::InitUploadLastDir();
1035 // Set up our default state. By default we're enabled (since we're a control
1036 // type that can be disabled but not actually disabled right now), optional,
1037 // read-write, and valid. Also by default we don't have to show validity UI
1038 // and so forth.
1039 AddStatesSilently(ElementState::ENABLED | ElementState::OPTIONAL_ |
1040 ElementState::VALID | ElementState::VALUE_EMPTY |
1041 ElementState::READWRITE);
1042 RemoveStatesSilently(ElementState::READONLY);
1043 UpdateApzAwareFlag();
1046 HTMLInputElement::~HTMLInputElement() {
1047 if (mNumberControlSpinnerIsSpinning) {
1048 StopNumberControlSpinnerSpin(eDisallowDispatchingEvents);
1050 nsImageLoadingContent::Destroy();
1051 FreeData();
1054 void HTMLInputElement::FreeData() {
1055 if (!IsSingleLineTextControl(false)) {
1056 free(mInputData.mValue);
1057 mInputData.mValue = nullptr;
1058 } else if (mInputData.mState) {
1059 // XXX Passing nullptr to UnbindFromFrame doesn't do anything!
1060 UnbindFromFrame(nullptr);
1061 mInputData.mState->Destroy();
1062 mInputData.mState = nullptr;
1065 if (mInputType) {
1066 mInputType->DropReference();
1067 mInputType = nullptr;
1071 void HTMLInputElement::EnsureEditorState() {
1072 MOZ_ASSERT(IsSingleLineTextControl(false));
1073 if (!mInputData.mState) {
1074 mInputData.mState = TextControlState::Construct(this);
1078 TextControlState* HTMLInputElement::GetEditorState() const {
1079 if (!IsSingleLineTextControl(false)) {
1080 return nullptr;
1083 // We've postponed allocating TextControlState, doing that in a const
1084 // method is fine.
1085 const_cast<HTMLInputElement*>(this)->EnsureEditorState();
1087 MOZ_ASSERT(mInputData.mState,
1088 "Single line text controls need to have a state"
1089 " associated with them");
1091 return mInputData.mState;
1094 // nsISupports
1096 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement)
1098 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
1099 TextControlElement)
1100 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
1101 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
1102 if (tmp->IsSingleLineTextControl(false) && tmp->mInputData.mState) {
1103 tmp->mInputData.mState->Traverse(cb);
1106 if (tmp->mFileData) {
1107 tmp->mFileData->Traverse(cb);
1109 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1111 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
1112 TextControlElement)
1113 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
1114 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
1115 if (tmp->IsSingleLineTextControl(false) && tmp->mInputData.mState) {
1116 tmp->mInputData.mState->Unlink();
1119 if (tmp->mFileData) {
1120 tmp->mFileData->Unlink();
1122 // XXX should unlink more?
1123 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1125 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLInputElement,
1126 TextControlElement,
1127 imgINotificationObserver,
1128 nsIImageLoadingContent,
1129 nsIConstraintValidation)
1131 // nsINode
1133 nsresult HTMLInputElement::Clone(dom::NodeInfo* aNodeInfo,
1134 nsINode** aResult) const {
1135 *aResult = nullptr;
1137 RefPtr<HTMLInputElement> it = new (aNodeInfo->NodeInfoManager())
1138 HTMLInputElement(do_AddRef(aNodeInfo), NOT_FROM_PARSER, FromClone::Yes);
1140 nsresult rv = const_cast<HTMLInputElement*>(this)->CopyInnerTo(it);
1141 NS_ENSURE_SUCCESS(rv, rv);
1143 switch (GetValueMode()) {
1144 case VALUE_MODE_VALUE:
1145 if (mValueChanged) {
1146 // We don't have our default value anymore. Set our value on
1147 // the clone.
1148 nsAutoString value;
1149 GetNonFileValueInternal(value);
1150 // SetValueInternal handles setting the VALUE_CHANGED bit for us
1151 if (NS_WARN_IF(
1152 NS_FAILED(rv = it->SetValueInternal(
1153 value, {ValueSetterOption::SetValueChanged})))) {
1154 return rv;
1157 break;
1158 case VALUE_MODE_FILENAME:
1159 if (it->OwnerDoc()->IsStaticDocument()) {
1160 // We're going to be used in print preview. Since the doc is static
1161 // we can just grab the pretty string and use it as wallpaper
1162 GetDisplayFileName(it->mFileData->mStaticDocFileList);
1163 } else {
1164 it->mFileData->ClearGetFilesHelpers();
1165 it->mFileData->mFilesOrDirectories.Clear();
1166 it->mFileData->mFilesOrDirectories.AppendElements(
1167 mFileData->mFilesOrDirectories);
1169 break;
1170 case VALUE_MODE_DEFAULT_ON:
1171 case VALUE_MODE_DEFAULT:
1172 break;
1175 if (mCheckedChanged) {
1176 // We no longer have our original checked state. Set our
1177 // checked state on the clone.
1178 it->DoSetChecked(mChecked, false, true);
1179 // Then tell DoneCreatingElement() not to overwrite:
1180 it->mShouldInitChecked = false;
1183 it->mIndeterminate = mIndeterminate;
1185 it->DoneCreatingElement();
1187 it->SetLastValueChangeWasInteractive(mLastValueChangeWasInteractive);
1188 it.forget(aResult);
1189 return NS_OK;
1192 void HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1193 const nsAttrValue* aValue, bool aNotify) {
1194 if (aNameSpaceID == kNameSpaceID_None) {
1195 if (aNotify && aName == nsGkAtoms::disabled) {
1196 mDisabledChanged = true;
1199 // When name or type changes, radio should be removed from radio group.
1200 // If we are not done creating the radio, we also should not do it.
1201 if (mType == FormControlType::InputRadio) {
1202 if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
1203 (mForm || mDoneCreating)) {
1204 RemoveFromRadioGroup();
1205 } else if (aName == nsGkAtoms::required) {
1206 auto* container = GetCurrentRadioGroupContainer();
1208 if (container && ((aValue && !HasAttr(aNameSpaceID, aName)) ||
1209 (!aValue && HasAttr(aNameSpaceID, aName)))) {
1210 nsAutoString name;
1211 GetAttr(nsGkAtoms::name, name);
1212 container->RadioRequiredWillChange(name, !!aValue);
1217 if (aName == nsGkAtoms::webkitdirectory) {
1218 Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED, true);
1222 return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
1223 aNameSpaceID, aName, aValue, aNotify);
1226 void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1227 const nsAttrValue* aValue,
1228 const nsAttrValue* aOldValue,
1229 nsIPrincipal* aSubjectPrincipal,
1230 bool aNotify) {
1231 if (aNameSpaceID == kNameSpaceID_None) {
1232 bool needValidityUpdate = false;
1233 if (aName == nsGkAtoms::src) {
1234 mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
1235 this, aValue ? aValue->GetStringValue() : EmptyString(),
1236 aSubjectPrincipal);
1237 if (aNotify && mType == FormControlType::InputImage) {
1238 if (aValue) {
1239 // Mark channel as urgent-start before load image if the image load is
1240 // initiated by a user interaction.
1241 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
1243 LoadImage(aValue->GetStringValue(), true, aNotify,
1244 eImageLoadType_Normal, mSrcTriggeringPrincipal);
1245 } else {
1246 // Null value means the attr got unset; drop the image
1247 CancelImageRequests(aNotify);
1252 if (aName == nsGkAtoms::value) {
1253 // If the element has a value in value mode, the value content attribute
1254 // is the default value. So if the elements value didn't change from the
1255 // default, we have to re-set it.
1256 if (!mValueChanged && GetValueMode() == VALUE_MODE_VALUE) {
1257 SetDefaultValueAsValue();
1258 } else if (GetValueMode() == VALUE_MODE_DEFAULT && HasDirAuto()) {
1259 SetAutoDirectionality(aNotify);
1261 // GetStepBase() depends on the `value` attribute if `min` is not present,
1262 // even if the value doesn't change.
1263 UpdateStepMismatchValidityState();
1264 needValidityUpdate = true;
1267 // Checked must be set no matter what type of control it is, since
1268 // mChecked must reflect the new value
1269 if (aName == nsGkAtoms::checked) {
1270 if (IsRadioOrCheckbox()) {
1271 SetStates(ElementState::DEFAULT, !!aValue, aNotify);
1273 if (!mCheckedChanged) {
1274 // Delay setting checked if we are creating this element (wait
1275 // until everything is set)
1276 if (!mDoneCreating) {
1277 mShouldInitChecked = true;
1278 } else {
1279 DoSetChecked(!!aValue, aNotify, false);
1282 needValidityUpdate = true;
1285 if (aName == nsGkAtoms::type) {
1286 FormControlType newType;
1287 if (!aValue) {
1288 // We're now a text input.
1289 newType = FormControlType(kInputDefaultType->value);
1290 } else {
1291 newType = FormControlType(aValue->GetEnumValue());
1293 if (newType != mType) {
1294 HandleTypeChange(newType, aNotify);
1295 needValidityUpdate = true;
1299 // When name or type changes, radio should be added to radio group.
1300 // If we are not done creating the radio, we also should not do it.
1301 if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
1302 mType == FormControlType::InputRadio && (mForm || mDoneCreating)) {
1303 AddToRadioGroup();
1304 UpdateValueMissingValidityStateForRadio(false);
1305 needValidityUpdate = true;
1308 if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
1309 aName == nsGkAtoms::readonly) {
1310 if (aName == nsGkAtoms::disabled) {
1311 // This *has* to be called *before* validity state check because
1312 // UpdateBarredFromConstraintValidation and
1313 // UpdateValueMissingValidityState depend on our disabled state.
1314 UpdateDisabledState(aNotify);
1317 if (aName == nsGkAtoms::required && DoesRequiredApply()) {
1318 // This *has* to be called *before* UpdateValueMissingValidityState
1319 // because UpdateValueMissingValidityState depends on our required
1320 // state.
1321 UpdateRequiredState(!!aValue, aNotify);
1324 if (aName == nsGkAtoms::readonly && !!aValue != !!aOldValue) {
1325 UpdateReadOnlyState(aNotify);
1328 UpdateValueMissingValidityState();
1330 // This *has* to be called *after* validity has changed.
1331 if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
1332 UpdateBarredFromConstraintValidation();
1334 needValidityUpdate = true;
1335 } else if (aName == nsGkAtoms::maxlength) {
1336 UpdateTooLongValidityState();
1337 needValidityUpdate = true;
1338 } else if (aName == nsGkAtoms::minlength) {
1339 UpdateTooShortValidityState();
1340 needValidityUpdate = true;
1341 } else if (aName == nsGkAtoms::pattern) {
1342 // Although pattern attribute only applies to single line text controls,
1343 // we set this flag for all input types to save having to check the type
1344 // here.
1345 mHasPatternAttribute = !!aValue;
1347 if (mDoneCreating) {
1348 UpdatePatternMismatchValidityState();
1350 needValidityUpdate = true;
1351 } else if (aName == nsGkAtoms::multiple) {
1352 UpdateTypeMismatchValidityState();
1353 needValidityUpdate = true;
1354 } else if (aName == nsGkAtoms::max) {
1355 UpdateHasRange(aNotify);
1356 mInputType->MinMaxStepAttrChanged();
1357 // Validity state must be updated *after* the UpdateValueDueToAttrChange
1358 // call above or else the following assert will not be valid.
1359 // We don't assert the state of underflow during creation since
1360 // DoneCreatingElement sanitizes.
1361 UpdateRangeOverflowValidityState();
1362 needValidityUpdate = true;
1363 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1364 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1365 "HTML5 spec does not allow underflow for type=range");
1366 } else if (aName == nsGkAtoms::min) {
1367 UpdateHasRange(aNotify);
1368 mInputType->MinMaxStepAttrChanged();
1369 // See corresponding @max comment
1370 UpdateRangeUnderflowValidityState();
1371 UpdateStepMismatchValidityState();
1372 needValidityUpdate = true;
1373 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1374 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1375 "HTML5 spec does not allow underflow for type=range");
1376 } else if (aName == nsGkAtoms::step) {
1377 mInputType->MinMaxStepAttrChanged();
1378 // See corresponding @max comment
1379 UpdateStepMismatchValidityState();
1380 needValidityUpdate = true;
1381 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1382 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1383 "HTML5 spec does not allow underflow for type=range");
1384 } else if (aName == nsGkAtoms::dir && aValue &&
1385 aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
1386 SetAutoDirectionality(aNotify);
1387 } else if (aName == nsGkAtoms::lang) {
1388 // FIXME(emilio, bug 1651070): This doesn't account for lang changes on
1389 // ancestors.
1390 if (mType == FormControlType::InputNumber) {
1391 // The validity of our value may have changed based on the locale.
1392 UpdateValidityState();
1393 needValidityUpdate = true;
1395 } else if (aName == nsGkAtoms::autocomplete) {
1396 // Clear the cached @autocomplete attribute and autocompleteInfo state.
1397 mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
1398 mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
1399 } else if (aName == nsGkAtoms::placeholder) {
1400 // Full addition / removals of the attribute reconstruct right now.
1401 if (nsTextControlFrame* f = do_QueryFrame(GetPrimaryFrame())) {
1402 f->PlaceholderChanged(aOldValue, aValue);
1404 UpdatePlaceholderShownState();
1405 needValidityUpdate = true;
1408 if (CreatesDateTimeWidget()) {
1409 if (aName == nsGkAtoms::value || aName == nsGkAtoms::readonly ||
1410 aName == nsGkAtoms::tabindex || aName == nsGkAtoms::required ||
1411 aName == nsGkAtoms::disabled) {
1412 // If original target is this and not the inner text control, we should
1413 // pass the focus to the inner text control.
1414 if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
1415 AsyncEventDispatcher::RunDOMEventWhenSafe(
1416 *dateTimeBoxElement,
1417 aName == nsGkAtoms::value ? u"MozDateTimeValueChanged"_ns
1418 : u"MozDateTimeAttributeChanged"_ns,
1419 CanBubble::eNo, ChromeOnlyDispatch::eNo);
1423 if (needValidityUpdate) {
1424 UpdateValidityElementStates(aNotify);
1428 return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
1429 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
1432 void HTMLInputElement::BeforeSetForm(HTMLFormElement* aForm, bool aBindToTree) {
1433 // No need to remove from radio group if we are just binding to tree.
1434 if (mType == FormControlType::InputRadio && !aBindToTree) {
1435 RemoveFromRadioGroup();
1438 // Dispatch event when <input> @form is set
1439 if (!aBindToTree) {
1440 MaybeDispatchLoginManagerEvents(aForm);
1444 void HTMLInputElement::AfterClearForm(bool aUnbindOrDelete) {
1445 MOZ_ASSERT(!mForm);
1447 // Do not add back to radio group if we are releasing or unbinding from tree.
1448 if (mType == FormControlType::InputRadio && !aUnbindOrDelete &&
1449 !GetCurrentRadioGroupContainer()) {
1450 AddToRadioGroup();
1451 UpdateValueMissingValidityStateForRadio(false);
1455 void HTMLInputElement::ResultForDialogSubmit(nsAString& aResult) {
1456 if (mType == FormControlType::InputImage) {
1457 // Get a property set by the frame to find out where it was clicked.
1458 const auto* lastClickedPoint =
1459 static_cast<CSSIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
1460 int32_t x, y;
1461 if (lastClickedPoint) {
1462 x = lastClickedPoint->x;
1463 y = lastClickedPoint->y;
1464 } else {
1465 x = y = 0;
1467 aResult.AppendInt(x);
1468 aResult.AppendLiteral(",");
1469 aResult.AppendInt(y);
1470 } else {
1471 GetAttr(nsGkAtoms::value, aResult);
1475 void HTMLInputElement::GetAutocomplete(nsAString& aValue) {
1476 if (!DoesAutocompleteApply()) {
1477 return;
1480 aValue.Truncate();
1481 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1483 mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(
1484 attributeVal, aValue, mAutocompleteAttrState);
1487 void HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo) {
1488 if (!DoesAutocompleteApply()) {
1489 aInfo.SetNull();
1490 return;
1493 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1494 mAutocompleteInfoState = nsContentUtils::SerializeAutocompleteAttribute(
1495 attributeVal, aInfo.SetValue(), mAutocompleteInfoState, true);
1498 void HTMLInputElement::GetCapture(nsAString& aValue) {
1499 GetEnumAttr(nsGkAtoms::capture, kCaptureDefault->tag, aValue);
1502 void HTMLInputElement::GetFormEnctype(nsAString& aValue) {
1503 GetEnumAttr(nsGkAtoms::formenctype, "", kFormDefaultEnctype->tag, aValue);
1506 void HTMLInputElement::GetFormMethod(nsAString& aValue) {
1507 GetEnumAttr(nsGkAtoms::formmethod, "", kFormDefaultMethod->tag, aValue);
1510 void HTMLInputElement::GetType(nsAString& aValue) const {
1511 GetEnumAttr(nsGkAtoms::type, kInputDefaultType->tag, aValue);
1514 int32_t HTMLInputElement::TabIndexDefault() { return 0; }
1516 uint32_t HTMLInputElement::Height() {
1517 if (mType != FormControlType::InputImage) {
1518 return 0;
1520 return GetWidthHeightForImage().height;
1523 void HTMLInputElement::SetIndeterminateInternal(bool aValue,
1524 bool aShouldInvalidate) {
1525 mIndeterminate = aValue;
1526 if (mType != FormControlType::InputCheckbox) {
1527 return;
1530 SetStates(ElementState::INDETERMINATE, aValue);
1532 if (aShouldInvalidate) {
1533 // Repaint the frame
1534 if (nsIFrame* frame = GetPrimaryFrame()) {
1535 frame->InvalidateFrameSubtree();
1540 void HTMLInputElement::SetIndeterminate(bool aValue) {
1541 SetIndeterminateInternal(aValue, true);
1544 uint32_t HTMLInputElement::Width() {
1545 if (mType != FormControlType::InputImage) {
1546 return 0;
1548 return GetWidthHeightForImage().width;
1551 bool HTMLInputElement::SanitizesOnValueGetter() const {
1552 // Don't return non-sanitized value for datetime types, email, or number.
1553 return mType == FormControlType::InputEmail ||
1554 mType == FormControlType::InputNumber || IsDateTimeInputType(mType);
1557 void HTMLInputElement::GetValue(nsAString& aValue, CallerType aCallerType) {
1558 GetValueInternal(aValue, aCallerType);
1560 // In the case where we need to sanitize an input value without affecting
1561 // the displayed user's input, we instead sanitize only on .value accesses.
1562 // For the more general case of input elements displaying text that isn't
1563 // their current value, see bug 805049.
1564 if (SanitizesOnValueGetter()) {
1565 SanitizeValue(aValue, SanitizationKind::ForValueGetter);
1569 void HTMLInputElement::GetValueInternal(nsAString& aValue,
1570 CallerType aCallerType) const {
1571 if (mType != FormControlType::InputFile) {
1572 GetNonFileValueInternal(aValue);
1573 return;
1576 if (aCallerType == CallerType::System) {
1577 aValue.Assign(mFileData->mFirstFilePath);
1578 return;
1581 if (mFileData->mFilesOrDirectories.IsEmpty()) {
1582 aValue.Truncate();
1583 return;
1586 nsAutoString file;
1587 GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], file);
1588 if (file.IsEmpty()) {
1589 aValue.Truncate();
1590 return;
1593 aValue.AssignLiteral("C:\\fakepath\\");
1594 aValue.Append(file);
1597 void HTMLInputElement::GetNonFileValueInternal(nsAString& aValue) const {
1598 switch (GetValueMode()) {
1599 case VALUE_MODE_VALUE:
1600 if (IsSingleLineTextControl(false)) {
1601 if (mInputData.mState) {
1602 mInputData.mState->GetValue(aValue, true, /* aForDisplay = */ false);
1603 } else {
1604 // Value hasn't been set yet.
1605 aValue.Truncate();
1607 } else if (!aValue.Assign(mInputData.mValue, fallible)) {
1608 aValue.Truncate();
1610 return;
1612 case VALUE_MODE_FILENAME:
1613 MOZ_ASSERT_UNREACHABLE("Someone screwed up here");
1614 // We'll just return empty string if someone does screw up.
1615 aValue.Truncate();
1616 return;
1618 case VALUE_MODE_DEFAULT:
1619 // Treat defaultValue as value.
1620 GetAttr(nsGkAtoms::value, aValue);
1621 return;
1623 case VALUE_MODE_DEFAULT_ON:
1624 // Treat default value as value and returns "on" if no value.
1625 if (!GetAttr(nsGkAtoms::value, aValue)) {
1626 aValue.AssignLiteral("on");
1628 return;
1632 void HTMLInputElement::ClearFiles(bool aSetValueChanged) {
1633 nsTArray<OwningFileOrDirectory> data;
1634 SetFilesOrDirectories(data, aSetValueChanged);
1637 int32_t HTMLInputElement::MonthsSinceJan1970(uint32_t aYear,
1638 uint32_t aMonth) const {
1639 return (aYear - 1970) * 12 + aMonth - 1;
1642 /* static */
1643 Decimal HTMLInputElement::StringToDecimal(const nsAString& aValue) {
1644 if (!IsAscii(aValue)) {
1645 return Decimal::nan();
1647 NS_LossyConvertUTF16toASCII asciiString(aValue);
1648 std::string stdString(asciiString.get(), asciiString.Length());
1649 auto decimal = Decimal::fromString(stdString);
1650 if (!decimal.isFinite()) {
1651 return Decimal::nan();
1653 // Numbers are considered finite IEEE 754 Double-precision floating point
1654 // values, but decimal supports a bigger range.
1655 static const Decimal maxDouble =
1656 Decimal::fromDouble(std::numeric_limits<double>::max());
1657 if (decimal < -maxDouble || decimal > maxDouble) {
1658 return Decimal::nan();
1660 return decimal;
1663 Decimal HTMLInputElement::GetValueAsDecimal() const {
1664 nsAutoString stringValue;
1665 GetNonFileValueInternal(stringValue);
1666 Decimal result = mInputType->ConvertStringToNumber(stringValue).mResult;
1667 return result.isFinite() ? result : Decimal::nan();
1670 void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
1671 ErrorResult& aRv) {
1672 // check security. Note that setting the value to the empty string is always
1673 // OK and gives pages a way to clear a file input if necessary.
1674 if (mType == FormControlType::InputFile) {
1675 if (!aValue.IsEmpty()) {
1676 if (aCallerType != CallerType::System) {
1677 // setting the value of a "FILE" input widget requires
1678 // chrome privilege
1679 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1680 return;
1682 Sequence<nsString> list;
1683 if (!list.AppendElement(aValue, fallible)) {
1684 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1685 return;
1688 MozSetFileNameArray(list, aRv);
1689 return;
1691 ClearFiles(true);
1692 } else {
1693 if (MayFireChangeOnBlur()) {
1694 // If the value has been set by a script, we basically want to keep the
1695 // current change event state. If the element is ready to fire a change
1696 // event, we should keep it that way. Otherwise, we should make sure the
1697 // element will not fire any event because of the script interaction.
1699 // NOTE: this is currently quite expensive work (too much string
1700 // manipulation). We should probably optimize that.
1701 nsAutoString currentValue;
1702 GetNonFileValueInternal(currentValue);
1704 nsresult rv = SetValueInternal(
1705 aValue, &currentValue,
1706 {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
1707 ValueSetterOption::MoveCursorToEndIfValueChanged});
1708 if (NS_FAILED(rv)) {
1709 aRv.Throw(rv);
1710 return;
1713 if (mFocusedValue.Equals(currentValue)) {
1714 GetValue(mFocusedValue, aCallerType);
1716 } else {
1717 nsresult rv = SetValueInternal(
1718 aValue,
1719 {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
1720 ValueSetterOption::MoveCursorToEndIfValueChanged});
1721 if (NS_FAILED(rv)) {
1722 aRv.Throw(rv);
1723 return;
1729 HTMLDataListElement* HTMLInputElement::GetList() const {
1730 nsAutoString dataListId;
1731 GetAttr(nsGkAtoms::list_, dataListId);
1732 if (dataListId.IsEmpty()) {
1733 return nullptr;
1736 DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
1737 if (!docOrShadow) {
1738 return nullptr;
1741 return HTMLDataListElement::FromNodeOrNull(
1742 docOrShadow->GetElementById(dataListId));
1745 void HTMLInputElement::SetValue(Decimal aValue, CallerType aCallerType) {
1746 MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!");
1748 if (aValue.isNaN()) {
1749 SetValue(u""_ns, aCallerType, IgnoreErrors());
1750 return;
1753 nsAutoString value;
1754 mInputType->ConvertNumberToString(aValue, value);
1755 SetValue(value, aCallerType, IgnoreErrors());
1758 void HTMLInputElement::GetValueAsDate(JSContext* aCx,
1759 JS::MutableHandle<JSObject*> aObject,
1760 ErrorResult& aRv) {
1761 aObject.set(nullptr);
1762 if (!IsDateTimeInputType(mType)) {
1763 return;
1766 Maybe<JS::ClippedTime> time;
1768 switch (mType) {
1769 case FormControlType::InputDate: {
1770 uint32_t year, month, day;
1771 nsAutoString value;
1772 GetNonFileValueInternal(value);
1773 if (!ParseDate(value, &year, &month, &day)) {
1774 return;
1777 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day)));
1778 break;
1780 case FormControlType::InputTime: {
1781 uint32_t millisecond;
1782 nsAutoString value;
1783 GetNonFileValueInternal(value);
1784 if (!ParseTime(value, &millisecond)) {
1785 return;
1788 time.emplace(JS::TimeClip(millisecond));
1789 MOZ_ASSERT(time->toDouble() == millisecond,
1790 "HTML times are restricted to the day after the epoch and "
1791 "never clip");
1792 break;
1794 case FormControlType::InputMonth: {
1795 uint32_t year, month;
1796 nsAutoString value;
1797 GetNonFileValueInternal(value);
1798 if (!ParseMonth(value, &year, &month)) {
1799 return;
1802 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, 1)));
1803 break;
1805 case FormControlType::InputWeek: {
1806 uint32_t year, week;
1807 nsAutoString value;
1808 GetNonFileValueInternal(value);
1809 if (!ParseWeek(value, &year, &week)) {
1810 return;
1813 double days = DaysSinceEpochFromWeek(year, week);
1814 time.emplace(JS::TimeClip(days * kMsPerDay));
1816 break;
1818 case FormControlType::InputDatetimeLocal: {
1819 uint32_t year, month, day, timeInMs;
1820 nsAutoString value;
1821 GetNonFileValueInternal(value);
1822 if (!ParseDateTimeLocal(value, &year, &month, &day, &timeInMs)) {
1823 return;
1826 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day, timeInMs)));
1827 break;
1829 default:
1830 break;
1833 if (time) {
1834 aObject.set(JS::NewDateObject(aCx, *time));
1835 if (!aObject) {
1836 aRv.NoteJSContextException(aCx);
1838 return;
1841 MOZ_ASSERT(false, "Unrecognized input type");
1842 aRv.Throw(NS_ERROR_UNEXPECTED);
1845 void HTMLInputElement::SetValueAsDate(JSContext* aCx,
1846 JS::Handle<JSObject*> aObj,
1847 ErrorResult& aRv) {
1848 if (!IsDateTimeInputType(mType)) {
1849 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1850 return;
1853 if (aObj) {
1854 bool isDate;
1855 if (!JS::ObjectIsDate(aCx, aObj, &isDate)) {
1856 aRv.NoteJSContextException(aCx);
1857 return;
1859 if (!isDate) {
1860 aRv.ThrowTypeError("Value being assigned is not a date.");
1861 return;
1865 double milliseconds;
1866 if (aObj) {
1867 if (!js::DateGetMsecSinceEpoch(aCx, aObj, &milliseconds)) {
1868 aRv.NoteJSContextException(aCx);
1869 return;
1871 } else {
1872 milliseconds = UnspecifiedNaN<double>();
1875 // At this point we know we're not a file input, so we can just pass "not
1876 // system" as the caller type, since the caller type only matters in the file
1877 // input case.
1878 if (std::isnan(milliseconds)) {
1879 SetValue(u""_ns, CallerType::NonSystem, aRv);
1880 return;
1883 if (mType != FormControlType::InputMonth) {
1884 SetValue(Decimal::fromDouble(milliseconds), CallerType::NonSystem);
1885 return;
1888 // type=month expects the value to be number of months.
1889 double year = JS::YearFromTime(milliseconds);
1890 double month = JS::MonthFromTime(milliseconds);
1892 if (std::isnan(year) || std::isnan(month)) {
1893 SetValue(u""_ns, CallerType::NonSystem, aRv);
1894 return;
1897 int32_t months = MonthsSinceJan1970(year, month + 1);
1898 SetValue(Decimal(int32_t(months)), CallerType::NonSystem);
1901 void HTMLInputElement::SetValueAsNumber(double aValueAsNumber,
1902 ErrorResult& aRv) {
1903 // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
1904 // bug 825197.
1905 if (std::isinf(aValueAsNumber)) {
1906 aRv.Throw(NS_ERROR_INVALID_ARG);
1907 return;
1910 if (!DoesValueAsNumberApply()) {
1911 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1912 return;
1915 // At this point we know we're not a file input, so we can just pass "not
1916 // system" as the caller type, since the caller type only matters in the file
1917 // input case.
1918 SetValue(Decimal::fromDouble(aValueAsNumber), CallerType::NonSystem);
1921 Decimal HTMLInputElement::GetMinimum() const {
1922 MOZ_ASSERT(
1923 DoesValueAsNumberApply(),
1924 "GetMinimum() should only be used for types that allow .valueAsNumber");
1926 // Only type=range has a default minimum
1927 Decimal defaultMinimum =
1928 mType == FormControlType::InputRange ? Decimal(0) : Decimal::nan();
1930 if (!HasAttr(nsGkAtoms::min)) {
1931 return defaultMinimum;
1934 nsAutoString minStr;
1935 GetAttr(nsGkAtoms::min, minStr);
1937 Decimal min = mInputType->ConvertStringToNumber(minStr).mResult;
1938 return min.isFinite() ? min : defaultMinimum;
1941 Decimal HTMLInputElement::GetMaximum() const {
1942 MOZ_ASSERT(
1943 DoesValueAsNumberApply(),
1944 "GetMaximum() should only be used for types that allow .valueAsNumber");
1946 // Only type=range has a default maximum
1947 Decimal defaultMaximum =
1948 mType == FormControlType::InputRange ? Decimal(100) : Decimal::nan();
1950 if (!HasAttr(nsGkAtoms::max)) {
1951 return defaultMaximum;
1954 nsAutoString maxStr;
1955 GetAttr(nsGkAtoms::max, maxStr);
1957 Decimal max = mInputType->ConvertStringToNumber(maxStr).mResult;
1958 return max.isFinite() ? max : defaultMaximum;
1961 Decimal HTMLInputElement::GetStepBase() const {
1962 MOZ_ASSERT(IsDateTimeInputType(mType) ||
1963 mType == FormControlType::InputNumber ||
1964 mType == FormControlType::InputRange,
1965 "Check that kDefaultStepBase is correct for this new type");
1966 // Do NOT use GetMinimum here - the spec says to use "the min content
1967 // attribute", not "the minimum".
1968 nsAutoString minStr;
1969 if (GetAttr(nsGkAtoms::min, minStr)) {
1970 Decimal min = mInputType->ConvertStringToNumber(minStr).mResult;
1971 if (min.isFinite()) {
1972 return min;
1976 // If @min is not a double, we should use @value.
1977 nsAutoString valueStr;
1978 if (GetAttr(nsGkAtoms::value, valueStr)) {
1979 Decimal value = mInputType->ConvertStringToNumber(valueStr).mResult;
1980 if (value.isFinite()) {
1981 return value;
1985 if (mType == FormControlType::InputWeek) {
1986 return kDefaultStepBaseWeek;
1989 return kDefaultStepBase;
1992 nsresult HTMLInputElement::GetValueIfStepped(int32_t aStep,
1993 StepCallerType aCallerType,
1994 Decimal* aNextStep) {
1995 if (!DoStepDownStepUpApply()) {
1996 return NS_ERROR_DOM_INVALID_STATE_ERR;
1999 Decimal stepBase = GetStepBase();
2000 Decimal step = GetStep();
2001 if (step == kStepAny) {
2002 if (aCallerType != CALLED_FOR_USER_EVENT) {
2003 return NS_ERROR_DOM_INVALID_STATE_ERR;
2005 // Allow the spin buttons and up/down arrow keys to do something sensible:
2006 step = GetDefaultStep();
2009 Decimal minimum = GetMinimum();
2010 Decimal maximum = GetMaximum();
2012 if (!maximum.isNaN()) {
2013 // "max - (max - stepBase) % step" is the nearest valid value to max.
2014 maximum = maximum - NS_floorModulo(maximum - stepBase, step);
2015 if (!minimum.isNaN()) {
2016 if (minimum > maximum) {
2017 // Either the minimum was greater than the maximum prior to our
2018 // adjustment to align maximum on a step, or else (if we adjusted
2019 // maximum) there is no valid step between minimum and the unadjusted
2020 // maximum.
2021 return NS_OK;
2026 Decimal value = GetValueAsDecimal();
2027 bool valueWasNaN = false;
2028 if (value.isNaN()) {
2029 value = Decimal(0);
2030 valueWasNaN = true;
2032 Decimal valueBeforeStepping = value;
2034 Decimal deltaFromStep = NS_floorModulo(value - stepBase, step);
2036 if (deltaFromStep != Decimal(0)) {
2037 if (aStep > 0) {
2038 value += step - deltaFromStep; // partial step
2039 value += step * Decimal(aStep - 1); // then remaining steps
2040 } else if (aStep < 0) {
2041 value -= deltaFromStep; // partial step
2042 value += step * Decimal(aStep + 1); // then remaining steps
2044 } else {
2045 value += step * Decimal(aStep);
2048 if (value < minimum) {
2049 value = minimum;
2050 deltaFromStep = NS_floorModulo(value - stepBase, step);
2051 if (deltaFromStep != Decimal(0)) {
2052 value += step - deltaFromStep;
2055 if (value > maximum) {
2056 value = maximum;
2057 deltaFromStep = NS_floorModulo(value - stepBase, step);
2058 if (deltaFromStep != Decimal(0)) {
2059 value -= deltaFromStep;
2063 if (!valueWasNaN && // value="", resulting in us using "0"
2064 ((aStep > 0 && value < valueBeforeStepping) ||
2065 (aStep < 0 && value > valueBeforeStepping))) {
2066 // We don't want step-up to effectively step down, or step-down to
2067 // effectively step up, so return;
2068 return NS_OK;
2071 *aNextStep = value;
2072 return NS_OK;
2075 nsresult HTMLInputElement::ApplyStep(int32_t aStep) {
2076 Decimal nextStep = Decimal::nan(); // unchanged if value will not change
2078 nsresult rv = GetValueIfStepped(aStep, CALLED_FOR_SCRIPT, &nextStep);
2080 if (NS_SUCCEEDED(rv) && nextStep.isFinite()) {
2081 // We know we're not a file input, so the caller type does not matter; just
2082 // pass "not system" to be safe.
2083 SetValue(nextStep, CallerType::NonSystem);
2086 return rv;
2089 bool HTMLInputElement::IsDateTimeInputType(FormControlType aType) {
2090 switch (aType) {
2091 case FormControlType::InputDate:
2092 case FormControlType::InputTime:
2093 case FormControlType::InputMonth:
2094 case FormControlType::InputWeek:
2095 case FormControlType::InputDatetimeLocal:
2096 return true;
2097 default:
2098 return false;
2102 void HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray,
2103 ErrorResult& aRv) {
2104 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2105 return;
2108 const nsTArray<OwningFileOrDirectory>& filesOrDirs =
2109 GetFilesOrDirectoriesInternal();
2110 for (uint32_t i = 0; i < filesOrDirs.Length(); i++) {
2111 nsAutoString str;
2112 GetDOMFileOrDirectoryPath(filesOrDirs[i], str, aRv);
2113 if (NS_WARN_IF(aRv.Failed())) {
2114 return;
2117 aArray.AppendElement(str);
2121 void HTMLInputElement::MozSetFileArray(
2122 const Sequence<OwningNonNull<File>>& aFiles) {
2123 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2124 return;
2127 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2128 MOZ_ASSERT(global);
2129 if (!global) {
2130 return;
2133 nsTArray<OwningFileOrDirectory> files;
2134 for (uint32_t i = 0; i < aFiles.Length(); ++i) {
2135 RefPtr<File> file = File::Create(global, aFiles[i].get()->Impl());
2136 if (NS_WARN_IF(!file)) {
2137 return;
2140 OwningFileOrDirectory* element = files.AppendElement();
2141 element->SetAsFile() = file;
2144 SetFilesOrDirectories(files, true);
2147 void HTMLInputElement::MozSetFileNameArray(const Sequence<nsString>& aFileNames,
2148 ErrorResult& aRv) {
2149 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2150 return;
2153 if (XRE_IsContentProcess()) {
2154 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
2155 return;
2158 nsTArray<OwningFileOrDirectory> files;
2159 for (uint32_t i = 0; i < aFileNames.Length(); ++i) {
2160 nsCOMPtr<nsIFile> file;
2162 if (StringBeginsWith(aFileNames[i], u"file:"_ns,
2163 nsASCIICaseInsensitiveStringComparator)) {
2164 // Converts the URL string into the corresponding nsIFile if possible
2165 // A local file will be created if the URL string begins with file://
2166 NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames[i]),
2167 getter_AddRefs(file));
2170 if (!file) {
2171 // this is no "file://", try as local file
2172 NS_NewLocalFile(aFileNames[i], false, getter_AddRefs(file));
2175 if (!file) {
2176 continue; // Not much we can do if the file doesn't exist
2179 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2180 if (!global) {
2181 aRv.Throw(NS_ERROR_FAILURE);
2182 return;
2185 RefPtr<File> domFile = File::CreateFromFile(global, file);
2186 if (NS_WARN_IF(!domFile)) {
2187 aRv.Throw(NS_ERROR_FAILURE);
2188 return;
2191 OwningFileOrDirectory* element = files.AppendElement();
2192 element->SetAsFile() = domFile;
2195 SetFilesOrDirectories(files, true);
2198 void HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
2199 ErrorResult& aRv) {
2200 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2201 return;
2204 nsCOMPtr<nsIFile> file;
2205 aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));
2206 if (NS_WARN_IF(aRv.Failed())) {
2207 return;
2210 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
2211 if (NS_WARN_IF(!window)) {
2212 aRv.Throw(NS_ERROR_FAILURE);
2213 return;
2216 RefPtr<Directory> directory = Directory::Create(window->AsGlobal(), file);
2217 MOZ_ASSERT(directory);
2219 nsTArray<OwningFileOrDirectory> array;
2220 OwningFileOrDirectory* element = array.AppendElement();
2221 element->SetAsDirectory() = directory;
2223 SetFilesOrDirectories(array, true);
2226 void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue) {
2227 if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
2228 return;
2231 aValue = *mDateTimeInputBoxValue;
2234 Element* HTMLInputElement::GetDateTimeBoxElement() {
2235 if (!GetShadowRoot()) {
2236 return nullptr;
2239 // The datetimebox <div> is the only child of the UA Widget Shadow Root
2240 // if it is present.
2241 MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
2242 MOZ_ASSERT(1 >= GetShadowRoot()->GetChildCount());
2243 if (nsIContent* inputAreaContent = GetShadowRoot()->GetFirstChild()) {
2244 return inputAreaContent->AsElement();
2247 return nullptr;
2250 void HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue) {
2251 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2252 return;
2255 mDateTimeInputBoxValue = MakeUnique<DateTimeValue>(aInitialValue);
2256 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2257 u"MozOpenDateTimePicker"_ns,
2258 CanBubble::eYes, Cancelable::eYes);
2261 void HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue) {
2262 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2263 return;
2266 mDateTimeInputBoxValue = MakeUnique<DateTimeValue>(aValue);
2267 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2268 u"MozUpdateDateTimePicker"_ns,
2269 CanBubble::eYes, Cancelable::eYes);
2272 void HTMLInputElement::CloseDateTimePicker() {
2273 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2274 return;
2277 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2278 u"MozCloseDateTimePicker"_ns,
2279 CanBubble::eYes, Cancelable::eYes);
2282 void HTMLInputElement::SetFocusState(bool aIsFocused) {
2283 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2284 return;
2286 SetStates(ElementState::FOCUS | ElementState::FOCUSRING, aIsFocused);
2289 void HTMLInputElement::UpdateValidityState() {
2290 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2291 return;
2294 // For now, datetime input box call this function only when the value may
2295 // become valid/invalid. For other validity states, they will be updated when
2296 // .value is actually changed.
2297 UpdateBadInputValidityState();
2298 UpdateValidityElementStates(true);
2301 bool HTMLInputElement::MozIsTextField(bool aExcludePassword) {
2302 // TODO: temporary until bug 888320 is fixed.
2304 // FIXME: Historically we never returned true for `number`, we should consider
2305 // changing that now that it is similar to other inputs.
2306 if (IsDateTimeInputType(mType) || mType == FormControlType::InputNumber) {
2307 return false;
2310 return IsSingleLineTextControl(aExcludePassword);
2313 void HTMLInputElement::SetUserInput(const nsAString& aValue,
2314 nsIPrincipal& aSubjectPrincipal) {
2315 AutoHandlingUserInputStatePusher inputStatePusher(true);
2317 if (mType == FormControlType::InputFile &&
2318 !aSubjectPrincipal.IsSystemPrincipal()) {
2319 return;
2322 if (mType == FormControlType::InputFile) {
2323 Sequence<nsString> list;
2324 if (!list.AppendElement(aValue, fallible)) {
2325 return;
2328 MozSetFileNameArray(list, IgnoreErrors());
2329 return;
2332 bool isInputEventDispatchedByTextControlState =
2333 GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false);
2335 nsresult rv = SetValueInternal(
2336 aValue,
2337 {ValueSetterOption::BySetUserInputAPI, ValueSetterOption::SetValueChanged,
2338 ValueSetterOption::MoveCursorToEndIfValueChanged});
2339 NS_ENSURE_SUCCESS_VOID(rv);
2341 if (!isInputEventDispatchedByTextControlState) {
2342 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
2343 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2344 "Failed to dispatch input event");
2347 // If this element is not currently focused, it won't receive a change event
2348 // for this update through the normal channels. So fire a change event
2349 // immediately, instead.
2350 if (CreatesDateTimeWidget() || !ShouldBlur(this)) {
2351 FireChangeEventIfNeeded();
2355 nsIEditor* HTMLInputElement::GetEditorForBindings() {
2356 if (!GetPrimaryFrame()) {
2357 // Ensure we construct frames (and thus an editor) if needed.
2358 GetPrimaryFrame(FlushType::Frames);
2360 return GetTextEditorFromState();
2363 bool HTMLInputElement::HasEditor() const {
2364 return !!GetTextEditorWithoutCreation();
2367 TextEditor* HTMLInputElement::GetTextEditorFromState() {
2368 TextControlState* state = GetEditorState();
2369 if (state) {
2370 return state->GetTextEditor();
2372 return nullptr;
2375 TextEditor* HTMLInputElement::GetTextEditor() {
2376 return GetTextEditorFromState();
2379 TextEditor* HTMLInputElement::GetTextEditorWithoutCreation() const {
2380 TextControlState* state = GetEditorState();
2381 if (!state) {
2382 return nullptr;
2384 return state->GetTextEditorWithoutCreation();
2387 nsISelectionController* HTMLInputElement::GetSelectionController() {
2388 TextControlState* state = GetEditorState();
2389 if (state) {
2390 return state->GetSelectionController();
2392 return nullptr;
2395 nsFrameSelection* HTMLInputElement::GetConstFrameSelection() {
2396 TextControlState* state = GetEditorState();
2397 if (state) {
2398 return state->GetConstFrameSelection();
2400 return nullptr;
2403 nsresult HTMLInputElement::BindToFrame(nsTextControlFrame* aFrame) {
2404 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
2405 TextControlState* state = GetEditorState();
2406 if (state) {
2407 return state->BindToFrame(aFrame);
2409 return NS_ERROR_FAILURE;
2412 void HTMLInputElement::UnbindFromFrame(nsTextControlFrame* aFrame) {
2413 TextControlState* state = GetEditorState();
2414 if (state && aFrame) {
2415 state->UnbindFromFrame(aFrame);
2419 nsresult HTMLInputElement::CreateEditor() {
2420 TextControlState* state = GetEditorState();
2421 if (state) {
2422 return state->PrepareEditor();
2424 return NS_ERROR_FAILURE;
2427 void HTMLInputElement::SetPreviewValue(const nsAString& aValue) {
2428 TextControlState* state = GetEditorState();
2429 if (state) {
2430 state->SetPreviewText(aValue, true);
2434 void HTMLInputElement::GetPreviewValue(nsAString& aValue) {
2435 TextControlState* state = GetEditorState();
2436 if (state) {
2437 state->GetPreviewText(aValue);
2441 void HTMLInputElement::EnablePreview() {
2442 if (mIsPreviewEnabled) {
2443 return;
2446 mIsPreviewEnabled = true;
2447 // Reconstruct the frame to append an anonymous preview node
2448 nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0},
2449 nsChangeHint_ReconstructFrame);
2452 bool HTMLInputElement::IsPreviewEnabled() { return mIsPreviewEnabled; }
2454 void HTMLInputElement::GetDisplayFileName(nsAString& aValue) const {
2455 MOZ_ASSERT(mFileData);
2457 if (OwnerDoc()->IsStaticDocument()) {
2458 aValue = mFileData->mStaticDocFileList;
2459 return;
2462 if (mFileData->mFilesOrDirectories.Length() == 1) {
2463 GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], aValue);
2464 return;
2467 nsAutoString value;
2469 if (mFileData->mFilesOrDirectories.IsEmpty()) {
2470 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2471 HasAttr(nsGkAtoms::webkitdirectory)) {
2472 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2473 "NoDirSelected", OwnerDoc(),
2474 value);
2475 } else if (HasAttr(nsGkAtoms::multiple)) {
2476 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2477 "NoFilesSelected", OwnerDoc(),
2478 value);
2479 } else {
2480 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2481 "NoFileSelected", OwnerDoc(),
2482 value);
2484 } else {
2485 nsString count;
2486 count.AppendInt(int(mFileData->mFilesOrDirectories.Length()));
2488 nsContentUtils::FormatMaybeLocalizedString(
2489 value, nsContentUtils::eFORMS_PROPERTIES, "XFilesSelected", OwnerDoc(),
2490 count);
2493 aValue = value;
2496 const nsTArray<OwningFileOrDirectory>&
2497 HTMLInputElement::GetFilesOrDirectoriesInternal() const {
2498 return mFileData->mFilesOrDirectories;
2501 void HTMLInputElement::SetFilesOrDirectories(
2502 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
2503 bool aSetValueChanged) {
2504 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2505 return;
2508 MOZ_ASSERT(mFileData);
2510 mFileData->ClearGetFilesHelpers();
2512 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2513 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2514 mFileData->mEntries.Clear();
2517 mFileData->mFilesOrDirectories.Clear();
2518 mFileData->mFilesOrDirectories.AppendElements(aFilesOrDirectories);
2520 AfterSetFilesOrDirectories(aSetValueChanged);
2523 void HTMLInputElement::SetFiles(FileList* aFiles, bool aSetValueChanged) {
2524 MOZ_ASSERT(mFileData);
2526 mFileData->mFilesOrDirectories.Clear();
2527 mFileData->ClearGetFilesHelpers();
2529 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2530 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2531 mFileData->mEntries.Clear();
2534 if (aFiles) {
2535 uint32_t listLength = aFiles->Length();
2536 for (uint32_t i = 0; i < listLength; i++) {
2537 OwningFileOrDirectory* element =
2538 mFileData->mFilesOrDirectories.AppendElement();
2539 element->SetAsFile() = aFiles->Item(i);
2543 AfterSetFilesOrDirectories(aSetValueChanged);
2546 // This method is used for testing only.
2547 void HTMLInputElement::MozSetDndFilesAndDirectories(
2548 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories) {
2549 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2550 return;
2553 SetFilesOrDirectories(aFilesOrDirectories, true);
2555 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2556 UpdateEntries(aFilesOrDirectories);
2559 RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
2560 new DispatchChangeEventCallback(this);
2562 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2563 HasAttr(nsGkAtoms::webkitdirectory)) {
2564 ErrorResult rv;
2565 GetFilesHelper* helper =
2566 GetOrCreateGetFilesHelper(true /* recursionFlag */, rv);
2567 if (NS_WARN_IF(rv.Failed())) {
2568 rv.SuppressException();
2569 return;
2572 helper->AddCallback(dispatchChangeEventCallback);
2573 } else {
2574 dispatchChangeEventCallback->DispatchEvents();
2578 void HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged) {
2579 // No need to flush here, if there's no frame at this point we
2580 // don't need to force creation of one just to tell it about this
2581 // new value. We just want the display to update as needed.
2582 nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
2583 if (formControlFrame) {
2584 nsAutoString readableValue;
2585 GetDisplayFileName(readableValue);
2586 formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue);
2589 // Grab the full path here for any chrome callers who access our .value via a
2590 // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
2591 // call under GetMozFullPath won't be rejected for not being urgent.
2592 if (mFileData->mFilesOrDirectories.IsEmpty()) {
2593 mFileData->mFirstFilePath.Truncate();
2594 } else {
2595 ErrorResult rv;
2596 GetDOMFileOrDirectoryPath(mFileData->mFilesOrDirectories[0],
2597 mFileData->mFirstFilePath, rv);
2598 if (NS_WARN_IF(rv.Failed())) {
2599 rv.SuppressException();
2603 // Null out |mFileData->mFileList| to return a new file list when asked for.
2604 // Don't clear it since the file list might come from the user via SetFiles.
2605 if (mFileData->mFileList) {
2606 mFileData->mFileList = nullptr;
2609 if (aSetValueChanged) {
2610 SetValueChanged(true);
2613 UpdateAllValidityStates(true);
2616 void HTMLInputElement::FireChangeEventIfNeeded() {
2617 if (!MayFireChangeOnBlur()) {
2618 return;
2621 // We're not exposing the GetValue return value anywhere here, so it's safe to
2622 // claim to be a system caller.
2623 nsAutoString value;
2624 GetValue(value, CallerType::System);
2626 // NOTE(emilio): Per spec we should not set this if we don't fire the change
2627 // event, but that seems like a bug. Using mValueChanged seems reasonable to
2628 // keep the expected behavior while
2629 // https://github.com/whatwg/html/issues/10013 is resolved.
2630 if (mValueChanged) {
2631 SetUserInteracted(true);
2633 if (mFocusedValue.Equals(value)) {
2634 return;
2636 // Dispatch the change event.
2637 mFocusedValue = value;
2638 nsContentUtils::DispatchTrustedEvent(
2639 OwnerDoc(), static_cast<nsIContent*>(this), u"change"_ns, CanBubble::eYes,
2640 Cancelable::eNo);
2643 FileList* HTMLInputElement::GetFiles() {
2644 if (mType != FormControlType::InputFile) {
2645 return nullptr;
2648 if (!mFileData->mFileList) {
2649 mFileData->mFileList = new FileList(static_cast<nsIContent*>(this));
2650 for (const OwningFileOrDirectory& item : GetFilesOrDirectoriesInternal()) {
2651 if (item.IsFile()) {
2652 mFileData->mFileList->Append(item.GetAsFile());
2657 return mFileData->mFileList;
2660 void HTMLInputElement::SetFiles(FileList* aFiles) {
2661 if (mType != FormControlType::InputFile || !aFiles) {
2662 return;
2665 // Update |mFileData->mFilesOrDirectories|
2666 SetFiles(aFiles, true);
2668 MOZ_ASSERT(!mFileData->mFileList, "Should've cleared the existing file list");
2670 // Update |mFileData->mFileList| without copy
2671 mFileData->mFileList = aFiles;
2674 /* static */
2675 void HTMLInputElement::HandleNumberControlSpin(void* aData) {
2676 RefPtr<HTMLInputElement> input = static_cast<HTMLInputElement*>(aData);
2678 NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
2679 "Should have called nsRepeatService::Stop()");
2681 nsNumberControlFrame* numberControlFrame =
2682 do_QueryFrame(input->GetPrimaryFrame());
2683 if (input->mType != FormControlType::InputNumber || !numberControlFrame) {
2684 // Type has changed (and possibly our frame type hasn't been updated yet)
2685 // or else we've lost our frame. Either way, stop the timer and don't do
2686 // anything else.
2687 input->StopNumberControlSpinnerSpin();
2688 } else {
2689 input->StepNumberControlForUserEvent(
2690 input->mNumberControlSpinnerSpinsUp ? 1 : -1);
2694 nsresult HTMLInputElement::SetValueInternal(
2695 const nsAString& aValue, const nsAString* aOldValue,
2696 const ValueSetterOptions& aOptions) {
2697 MOZ_ASSERT(GetValueMode() != VALUE_MODE_FILENAME,
2698 "Don't call SetValueInternal for file inputs");
2700 // We want to remember if the SetValueInternal() call is being made for a XUL
2701 // element. We do that by looking at the parent node here, and if that node
2702 // is a XUL node, we consider our control a XUL control. XUL controls preserve
2703 // edit history across value setters.
2705 // TODO(emilio): Rather than doing this maybe add an attribute instead and
2706 // read it only on chrome docs or something? That'd allow front-end code to
2707 // move away from xul without weird side-effects.
2708 const bool forcePreserveUndoHistory = mParent && mParent->IsXULElement();
2710 switch (GetValueMode()) {
2711 case VALUE_MODE_VALUE: {
2712 // At the moment, only single line text control have to sanitize their
2713 // value Because we have to create a new string for that, we should
2714 // prevent doing it if it's useless.
2715 nsAutoString value(aValue);
2717 if (mDoneCreating &&
2718 !(mType == FormControlType::InputNumber &&
2719 aOptions.contains(ValueSetterOption::BySetUserInputAPI))) {
2720 // When the value of a number input is set by a script, we need to make
2721 // sure the value is a valid floating-point number.
2722 // https://html.spec.whatwg.org/#valid-floating-point-number
2723 // When it's set by a user, however, we need to be more permissive, so
2724 // we don't sanitize its value here. See bug 1839572.
2725 SanitizeValue(value, SanitizationKind::ForValueSetter);
2727 // else DoneCreatingElement calls us again once mDoneCreating is true
2729 const bool setValueChanged =
2730 aOptions.contains(ValueSetterOption::SetValueChanged);
2731 if (setValueChanged) {
2732 SetValueChanged(true);
2735 if (IsSingleLineTextControl(false)) {
2736 // Note that if aOptions includes
2737 // ValueSetterOption::BySetUserInputAPI, "input" event is automatically
2738 // dispatched by TextControlState::SetValue(). If you'd change condition
2739 // of calling this method, you need to maintain SetUserInput() too. FYI:
2740 // After calling SetValue(), the input type might have been
2741 // modified so that mInputData may not store TextControlState.
2742 EnsureEditorState();
2743 if (!mInputData.mState->SetValue(
2744 value, aOldValue,
2745 forcePreserveUndoHistory
2746 ? aOptions + ValueSetterOption::PreserveUndoHistory
2747 : aOptions)) {
2748 return NS_ERROR_OUT_OF_MEMORY;
2750 // If the caller won't dispatch "input" event via
2751 // nsContentUtils::DispatchInputEvent(), we need to modify
2752 // validationMessage value here.
2754 // FIXME(emilio): ValueSetterOption::ByInternalAPI is not supposed to
2755 // change state, but maybe we could run this too?
2756 if (aOptions.contains(ValueSetterOption::ByContentAPI)) {
2757 MaybeUpdateAllValidityStates(!mDoneCreating);
2759 } else {
2760 free(mInputData.mValue);
2761 mInputData.mValue = ToNewUnicode(value);
2762 if (setValueChanged) {
2763 SetValueChanged(true);
2765 if (mType == FormControlType::InputRange) {
2766 nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
2767 if (frame) {
2768 frame->UpdateForValueChange();
2770 } else if (CreatesDateTimeWidget() &&
2771 !aOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
2772 if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
2773 AsyncEventDispatcher::RunDOMEventWhenSafe(
2774 *dateTimeBoxElement, u"MozDateTimeValueChanged"_ns,
2775 CanBubble::eNo, ChromeOnlyDispatch::eNo);
2778 if (mDoneCreating) {
2779 OnValueChanged(ValueChangeKind::Internal, value.IsEmpty(), &value);
2781 // else DoneCreatingElement calls us again once mDoneCreating is true
2784 if (mType == FormControlType::InputColor) {
2785 // Update color frame, to reflect color changes
2786 nsColorControlFrame* colorControlFrame =
2787 do_QueryFrame(GetPrimaryFrame());
2788 if (colorControlFrame) {
2789 colorControlFrame->UpdateColor();
2792 return NS_OK;
2795 case VALUE_MODE_DEFAULT:
2796 case VALUE_MODE_DEFAULT_ON:
2797 // If the value of a hidden input was changed, we mark it changed so that
2798 // we will know we need to save / restore the value. Yes, we are
2799 // overloading the meaning of ValueChanged just a teensy bit to save a
2800 // measly byte of storage space in HTMLInputElement. Yes, you are free to
2801 // make a new flag, NEED_TO_SAVE_VALUE, at such time as mBitField becomes
2802 // a 16-bit value.
2803 if (mType == FormControlType::InputHidden) {
2804 SetValueChanged(true);
2807 // Make sure to keep track of the last value change not being interactive,
2808 // just in case this used to be another kind of editable input before.
2809 // Note that a checked change _could_ really be interactive, but we don't
2810 // keep track of that elsewhere so seems fine to just do this.
2811 SetLastValueChangeWasInteractive(false);
2813 // Treat value == defaultValue for other input elements.
2814 return nsGenericHTMLFormControlElementWithState::SetAttr(
2815 kNameSpaceID_None, nsGkAtoms::value, aValue, true);
2817 case VALUE_MODE_FILENAME:
2818 return NS_ERROR_UNEXPECTED;
2821 // This return statement is required for some compilers.
2822 return NS_OK;
2825 void HTMLInputElement::SetValueChanged(bool aValueChanged) {
2826 if (mValueChanged == aValueChanged) {
2827 return;
2829 mValueChanged = aValueChanged;
2830 UpdateTooLongValidityState();
2831 UpdateTooShortValidityState();
2832 UpdateValidityElementStates(true);
2835 void HTMLInputElement::SetLastValueChangeWasInteractive(bool aWasInteractive) {
2836 if (aWasInteractive == mLastValueChangeWasInteractive) {
2837 return;
2839 mLastValueChangeWasInteractive = aWasInteractive;
2840 const bool wasValid = IsValid();
2841 UpdateTooLongValidityState();
2842 UpdateTooShortValidityState();
2843 if (wasValid != IsValid()) {
2844 UpdateValidityElementStates(true);
2848 void HTMLInputElement::SetCheckedChanged(bool aCheckedChanged) {
2849 DoSetCheckedChanged(aCheckedChanged, true);
2852 void HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged, bool aNotify) {
2853 if (mType == FormControlType::InputRadio) {
2854 if (mCheckedChanged != aCheckedChanged) {
2855 nsCOMPtr<nsIRadioVisitor> visitor =
2856 new nsRadioSetCheckedChangedVisitor(aCheckedChanged);
2857 VisitGroup(visitor);
2859 } else {
2860 SetCheckedChangedInternal(aCheckedChanged);
2864 void HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged) {
2865 if (mCheckedChanged == aCheckedChanged) {
2866 return;
2868 mCheckedChanged = aCheckedChanged;
2869 UpdateValidityElementStates(true);
2872 void HTMLInputElement::SetChecked(bool aChecked) {
2873 DoSetChecked(aChecked, true, true);
2876 void HTMLInputElement::DoSetChecked(bool aChecked, bool aNotify,
2877 bool aSetValueChanged) {
2878 // If the user or JS attempts to set checked, whether it actually changes the
2879 // value or not, we say the value was changed so that defaultValue don't
2880 // affect it no more.
2881 if (aSetValueChanged) {
2882 DoSetCheckedChanged(true, aNotify);
2885 // Don't do anything if we're not changing whether it's checked (it would
2886 // screw up state actually, especially when you are setting radio button to
2887 // false)
2888 if (mChecked == aChecked) {
2889 return;
2892 // Set checked
2893 if (mType != FormControlType::InputRadio) {
2894 SetCheckedInternal(aChecked, aNotify);
2895 return;
2898 // For radio button, we need to do some extra fun stuff
2899 if (aChecked) {
2900 RadioSetChecked(aNotify);
2901 return;
2904 if (auto* container = GetCurrentRadioGroupContainer()) {
2905 nsAutoString name;
2906 GetAttr(nsGkAtoms::name, name);
2907 container->SetCurrentRadioButton(name, nullptr);
2909 // SetCheckedInternal is going to ask all radios to update their
2910 // validity state. We have to be sure the radio group container knows
2911 // the currently selected radio.
2912 SetCheckedInternal(false, aNotify);
2915 void HTMLInputElement::RadioSetChecked(bool aNotify) {
2916 // Find the selected radio button so we can deselect it
2917 HTMLInputElement* currentlySelected = GetSelectedRadioButton();
2919 // Deselect the currently selected radio button
2920 if (currentlySelected) {
2921 // Pass true for the aNotify parameter since the currently selected
2922 // button is already in the document.
2923 currentlySelected->SetCheckedInternal(false, true);
2926 // Let the group know that we are now the One True Radio Button
2927 if (auto* container = GetCurrentRadioGroupContainer()) {
2928 nsAutoString name;
2929 GetAttr(nsGkAtoms::name, name);
2930 container->SetCurrentRadioButton(name, this);
2933 // SetCheckedInternal is going to ask all radios to update their
2934 // validity state.
2935 SetCheckedInternal(true, aNotify);
2938 RadioGroupContainer* HTMLInputElement::GetCurrentRadioGroupContainer() const {
2939 NS_ASSERTION(
2940 mType == FormControlType::InputRadio,
2941 "GetRadioGroupContainer should only be called when type='radio'");
2942 return mRadioGroupContainer;
2945 RadioGroupContainer* HTMLInputElement::FindTreeRadioGroupContainer() const {
2946 nsAutoString name;
2947 GetAttr(nsGkAtoms::name, name);
2949 if (name.IsEmpty()) {
2950 return nullptr;
2952 if (mForm) {
2953 return &mForm->OwnedRadioGroupContainer();
2955 if (IsInNativeAnonymousSubtree()) {
2956 return nullptr;
2958 if (Document* doc = GetUncomposedDoc()) {
2959 return &doc->OwnedRadioGroupContainer();
2961 return &static_cast<FragmentOrElement*>(SubtreeRoot())
2962 ->OwnedRadioGroupContainer();
2965 void HTMLInputElement::DisconnectRadioGroupContainer() {
2966 mRadioGroupContainer = nullptr;
2969 HTMLInputElement* HTMLInputElement::GetSelectedRadioButton() const {
2970 auto* container = GetCurrentRadioGroupContainer();
2971 if (!container) {
2972 return nullptr;
2975 nsAutoString name;
2976 GetAttr(nsGkAtoms::name, name);
2978 return container->GetCurrentRadioButton(name);
2981 void HTMLInputElement::MaybeSubmitForm(nsPresContext* aPresContext) {
2982 if (!mForm) {
2983 // Nothing to do here.
2984 return;
2987 RefPtr<PresShell> presShell = aPresContext->GetPresShell();
2988 if (!presShell) {
2989 return;
2992 // Get the default submit element
2993 if (RefPtr<nsGenericHTMLFormElement> submitContent =
2994 mForm->GetDefaultSubmitElement()) {
2995 WidgetMouseEvent event(true, eMouseClick, nullptr, WidgetMouseEvent::eReal);
2996 nsEventStatus status = nsEventStatus_eIgnore;
2997 presShell->HandleDOMEventWithTarget(submitContent, &event, &status);
2998 } else if (!mForm->ImplicitSubmissionIsDisabled()) {
2999 // If there's only one text control, just submit the form
3000 // Hold strong ref across the event
3001 RefPtr<dom::HTMLFormElement> form(mForm);
3002 form->MaybeSubmit(nullptr);
3006 void HTMLInputElement::UpdateCheckedState(bool aNotify) {
3007 SetStates(ElementState::CHECKED, IsRadioOrCheckbox() && mChecked, aNotify);
3010 void HTMLInputElement::UpdateIndeterminateState(bool aNotify) {
3011 bool indeterminate = [&] {
3012 if (mType == FormControlType::InputCheckbox) {
3013 return mIndeterminate;
3015 if (mType == FormControlType::InputRadio) {
3016 return !mChecked && !GetSelectedRadioButton();
3018 return false;
3019 }();
3020 SetStates(ElementState::INDETERMINATE, indeterminate, aNotify);
3023 void HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify) {
3024 // Set the value
3025 mChecked = aChecked;
3027 if (IsRadioOrCheckbox()) {
3028 SetStates(ElementState::CHECKED, aChecked, aNotify);
3031 // No need to update element state, since we're about to call
3032 // UpdateState anyway.
3033 UpdateAllValidityStatesButNotElementState();
3034 UpdateIndeterminateState(aNotify);
3035 UpdateValidityElementStates(aNotify);
3037 // Notify all radios in the group that value has changed, this is to let
3038 // radios to have the chance to update its states, e.g., :indeterminate.
3039 if (mType == FormControlType::InputRadio) {
3040 nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
3041 VisitGroup(visitor);
3045 #if !defined(ANDROID) && !defined(XP_MACOSX)
3046 bool HTMLInputElement::IsNodeApzAwareInternal() const {
3047 // Tell APZC we may handle mouse wheel event and do preventDefault when input
3048 // type is number.
3049 return mType == FormControlType::InputNumber ||
3050 mType == FormControlType::InputRange ||
3051 nsINode::IsNodeApzAwareInternal();
3053 #endif
3055 bool HTMLInputElement::IsInteractiveHTMLContent() const {
3056 return mType != FormControlType::InputHidden ||
3057 nsGenericHTMLFormControlElementWithState::IsInteractiveHTMLContent();
3060 void HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
3061 nsImageLoadingContent::AsyncEventRunning(aEvent);
3064 void HTMLInputElement::Select() {
3065 if (!IsSingleLineTextControl(false)) {
3066 return;
3069 TextControlState* state = GetEditorState();
3070 MOZ_ASSERT(state, "Single line text controls are expected to have a state");
3072 if (FocusState() != FocusTristate::eUnfocusable) {
3073 RefPtr<nsFrameSelection> fs = state->GetConstFrameSelection();
3074 if (fs && fs->MouseDownRecorded()) {
3075 // This means that we're being called while the frame selection has a
3076 // mouse down event recorded to adjust the caret during the mouse up
3077 // event. We are probably called from the focus event handler. We should
3078 // override the delayed caret data in this case to ensure that this
3079 // select() call takes effect.
3080 fs->SetDelayedCaretData(nullptr);
3083 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
3084 fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
3086 // A focus event handler may change the type attribute, which will destroy
3087 // the previous state object.
3088 state = GetEditorState();
3089 if (!state) {
3090 return;
3095 // Directly call TextControlState::SetSelectionRange because
3096 // HTMLInputElement::SetSelectionRange only applies to fewer types
3097 state->SetSelectionRange(0, UINT32_MAX, Optional<nsAString>(), IgnoreErrors(),
3098 TextControlState::ScrollAfterSelection::No);
3101 void HTMLInputElement::SelectAll(nsPresContext* aPresContext) {
3102 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
3104 if (formControlFrame) {
3105 formControlFrame->SetFormProperty(nsGkAtoms::select, u""_ns);
3109 bool HTMLInputElement::NeedToInitializeEditorForEvent(
3110 EventChainPreVisitor& aVisitor) const {
3111 // We only need to initialize the editor for single line input controls
3112 // because they are lazily initialized. We don't need to initialize the
3113 // control for certain types of events, because we know that those events are
3114 // safe to be handled without the editor being initialized. These events
3115 // include: mousein/move/out, overflow/underflow, DOM mutation, and void
3116 // events. Void events are dispatched frequently by async keyboard scrolling
3117 // to focused elements, so it's important to handle them to prevent excessive
3118 // DOM mutations.
3119 if (!IsSingleLineTextControl(false) ||
3120 aVisitor.mEvent->mClass == eMutationEventClass) {
3121 return false;
3124 switch (aVisitor.mEvent->mMessage) {
3125 case eVoidEvent:
3126 case eMouseMove:
3127 case eMouseEnterIntoWidget:
3128 case eMouseExitFromWidget:
3129 case eMouseOver:
3130 case eMouseOut:
3131 case eScrollPortUnderflow:
3132 case eScrollPortOverflow:
3133 return false;
3134 default:
3135 return true;
3139 bool HTMLInputElement::IsDisabledForEvents(WidgetEvent* aEvent) {
3140 return IsElementDisabledForEvents(aEvent, GetPrimaryFrame());
3143 bool HTMLInputElement::CheckActivationBehaviorPreconditions(
3144 EventChainVisitor& aVisitor) const {
3145 switch (mType) {
3146 case FormControlType::InputColor:
3147 case FormControlType::InputCheckbox:
3148 case FormControlType::InputRadio:
3149 case FormControlType::InputFile:
3150 case FormControlType::InputSubmit:
3151 case FormControlType::InputImage:
3152 case FormControlType::InputReset:
3153 case FormControlType::InputButton: {
3154 // Track whether we're in the outermost Dispatch invocation that will
3155 // cause activation of the input. That is, if we're a click event, or a
3156 // DOMActivate that was dispatched directly, this will be set, but if
3157 // we're a DOMActivate dispatched from click handling, it will not be set.
3158 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3159 bool outerActivateEvent =
3160 (mouseEvent && mouseEvent->IsLeftClickEvent()) ||
3161 (aVisitor.mEvent->mMessage == eLegacyDOMActivate &&
3162 !mInInternalActivate);
3163 if (outerActivateEvent) {
3164 aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
3166 return outerActivateEvent;
3168 default:
3169 return false;
3173 void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
3174 // Do not process any DOM events if the element is disabled
3175 aVisitor.mCanHandle = false;
3176 if (IsDisabledForEvents(aVisitor.mEvent)) {
3177 return;
3180 // Initialize the editor if needed.
3181 if (NeedToInitializeEditorForEvent(aVisitor)) {
3182 nsITextControlFrame* textControlFrame = do_QueryFrame(GetPrimaryFrame());
3183 if (textControlFrame) textControlFrame->EnsureEditorInitialized();
3186 if (CheckActivationBehaviorPreconditions(aVisitor)) {
3187 aVisitor.mWantsActivationBehavior = true;
3190 // We must cache type because mType may change during JS event (bug 2369)
3191 aVisitor.mItemFlags |= uint8_t(mType);
3193 if (aVisitor.mEvent->mMessage == eFocus && aVisitor.mEvent->IsTrusted() &&
3194 MayFireChangeOnBlur() &&
3195 // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
3196 // we get the 'focus' event.
3197 !mIsDraggingRange) {
3198 GetValue(mFocusedValue, CallerType::System);
3201 // Fire onchange (if necessary), before we do the blur, bug 357684.
3202 if (aVisitor.mEvent->mMessage == eBlur) {
3203 // We set NS_PRE_HANDLE_BLUR_EVENT here and handle it in PreHandleEvent to
3204 // prevent breaking event target chain creation.
3205 aVisitor.mWantsPreHandleEvent = true;
3206 aVisitor.mItemFlags |= NS_PRE_HANDLE_BLUR_EVENT;
3209 if (mType == FormControlType::InputRange &&
3210 (aVisitor.mEvent->mMessage == eFocus ||
3211 aVisitor.mEvent->mMessage == eBlur)) {
3212 // Just as nsGenericHTMLFormControlElementWithState::GetEventTargetParent
3213 // calls nsIFormControlFrame::SetFocus, we handle focus here.
3214 nsIFrame* frame = GetPrimaryFrame();
3215 if (frame) {
3216 frame->InvalidateFrameSubtree();
3220 if (mType == FormControlType::InputNumber && aVisitor.mEvent->IsTrusted()) {
3221 if (mNumberControlSpinnerIsSpinning) {
3222 // If the timer is running the user has depressed the mouse on one of the
3223 // spin buttons. If the mouse exits the button we either want to reverse
3224 // the direction of spin if it has moved over the other button, or else
3225 // we want to end the spin. We do this here (rather than in
3226 // PostHandleEvent) because we don't want to let content preventDefault()
3227 // the end of the spin.
3228 if (aVisitor.mEvent->mMessage == eMouseMove) {
3229 // Be aggressive about stopping the spin:
3230 bool stopSpin = true;
3231 nsNumberControlFrame* numberControlFrame =
3232 do_QueryFrame(GetPrimaryFrame());
3233 if (numberControlFrame) {
3234 bool oldNumberControlSpinTimerSpinsUpValue =
3235 mNumberControlSpinnerSpinsUp;
3236 switch (numberControlFrame->GetSpinButtonForPointerEvent(
3237 aVisitor.mEvent->AsMouseEvent())) {
3238 case nsNumberControlFrame::eSpinButtonUp:
3239 mNumberControlSpinnerSpinsUp = true;
3240 stopSpin = false;
3241 break;
3242 case nsNumberControlFrame::eSpinButtonDown:
3243 mNumberControlSpinnerSpinsUp = false;
3244 stopSpin = false;
3245 break;
3247 if (mNumberControlSpinnerSpinsUp !=
3248 oldNumberControlSpinTimerSpinsUpValue) {
3249 nsNumberControlFrame* numberControlFrame =
3250 do_QueryFrame(GetPrimaryFrame());
3251 if (numberControlFrame) {
3252 numberControlFrame->SpinnerStateChanged();
3256 if (stopSpin) {
3257 StopNumberControlSpinnerSpin();
3259 } else if (aVisitor.mEvent->mMessage == eMouseUp) {
3260 StopNumberControlSpinnerSpin();
3265 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor);
3267 // Stop the event if the related target's first non-native ancestor is the
3268 // same as the original target's first non-native ancestor (we are moving
3269 // inside of the same element).
3271 // FIXME(emilio): Is this still needed now that we use Shadow DOM for this?
3272 if (CreatesDateTimeWidget() && aVisitor.mEvent->IsTrusted() &&
3273 (aVisitor.mEvent->mMessage == eFocus ||
3274 aVisitor.mEvent->mMessage == eFocusIn ||
3275 aVisitor.mEvent->mMessage == eFocusOut ||
3276 aVisitor.mEvent->mMessage == eBlur)) {
3277 nsIContent* originalTarget = nsIContent::FromEventTargetOrNull(
3278 aVisitor.mEvent->AsFocusEvent()->mOriginalTarget);
3279 nsIContent* relatedTarget = nsIContent::FromEventTargetOrNull(
3280 aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
3282 if (originalTarget && relatedTarget &&
3283 originalTarget->FindFirstNonChromeOnlyAccessContent() ==
3284 relatedTarget->FindFirstNonChromeOnlyAccessContent()) {
3285 aVisitor.mCanHandle = false;
3290 void HTMLInputElement::LegacyPreActivationBehavior(
3291 EventChainVisitor& aVisitor) {
3293 // Web pages expect the value of a radio button or checkbox to be set
3294 // *before* onclick and DOMActivate fire, and they expect that if they set
3295 // the value explicitly during onclick or DOMActivate it will not be toggled
3296 // or any such nonsense.
3297 // In order to support that (bug 57137 and 58460 are examples) we toggle
3298 // the checked attribute *first*, and then fire onclick. If the user
3299 // returns false, we reset the control to the old checked value. Otherwise,
3300 // we dispatch DOMActivate. If DOMActivate is cancelled, we also reset
3301 // the control to the old checked value. We need to keep track of whether
3302 // we've already toggled the state from onclick since the user could
3303 // explicitly dispatch DOMActivate on the element.
3305 // These are compatibility hacks and are defined as legacy-pre-activation
3306 // and legacy-canceled-activation behavior in HTML.
3309 // Assert mType didn't change after GetEventTargetParent
3310 MOZ_ASSERT(NS_CONTROL_TYPE(aVisitor.mItemFlags) == uint8_t(mType));
3312 bool originalCheckedValue = false;
3313 mCheckedIsToggled = false;
3315 if (mType == FormControlType::InputCheckbox) {
3316 if (mIndeterminate) {
3317 // indeterminate is always set to FALSE when the checkbox is toggled
3318 SetIndeterminateInternal(false, false);
3319 aVisitor.mItemFlags |= NS_ORIGINAL_INDETERMINATE_VALUE;
3322 originalCheckedValue = Checked();
3323 DoSetChecked(!originalCheckedValue, true, true);
3324 mCheckedIsToggled = true;
3326 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
3327 aVisitor.mEventStatus = nsEventStatus_eConsumeDoDefault;
3329 } else if (mType == FormControlType::InputRadio) {
3330 HTMLInputElement* selectedRadioButton = GetSelectedRadioButton();
3331 aVisitor.mItemData = static_cast<Element*>(selectedRadioButton);
3333 originalCheckedValue = Checked();
3334 if (!originalCheckedValue) {
3335 DoSetChecked(true, true, true);
3336 mCheckedIsToggled = true;
3339 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
3340 aVisitor.mEventStatus = nsEventStatus_eConsumeDoDefault;
3344 if (originalCheckedValue) {
3345 aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
3348 // out-of-spec legacy pre-activation behavior needed because of bug 1803805
3349 if ((mType == FormControlType::InputSubmit ||
3350 mType == FormControlType::InputImage) &&
3351 mForm) {
3352 aVisitor.mItemFlags |= NS_IN_SUBMIT_CLICK;
3353 aVisitor.mItemData = static_cast<Element*>(mForm);
3354 // tell the form that we are about to enter a click handler.
3355 // that means that if there are scripted submissions, the
3356 // latest one will be deferred until after the exit point of the
3357 // handler.
3358 mForm->OnSubmitClickBegin(this);
3362 nsresult HTMLInputElement::PreHandleEvent(EventChainVisitor& aVisitor) {
3363 if (aVisitor.mItemFlags & NS_PRE_HANDLE_BLUR_EVENT) {
3364 MOZ_ASSERT(aVisitor.mEvent->mMessage == eBlur);
3365 FireChangeEventIfNeeded();
3367 return nsGenericHTMLFormControlElementWithState::PreHandleEvent(aVisitor);
3370 void HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent) {
3371 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
3372 if (!rangeFrame) {
3373 return;
3376 mIsDraggingRange = true;
3377 mRangeThumbDragStartValue = GetValueAsDecimal();
3378 // Don't use CaptureFlags::RetargetToElement, as that breaks pseudo-class
3379 // styling of the thumb.
3380 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
3382 // Before we change the value, record the current value so that we'll
3383 // correctly send a 'change' event if appropriate. We need to do this here
3384 // because the 'focus' event is handled after the 'mousedown' event that
3385 // we're being called for (i.e. too late to update mFocusedValue, since we'll
3386 // have changed it by then).
3387 GetValue(mFocusedValue, CallerType::System);
3389 SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent),
3390 SnapToTickMarks::Yes);
3393 void HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent* aEvent) {
3394 MOZ_ASSERT(mIsDraggingRange);
3396 if (PresShell::GetCapturingContent() == this) {
3397 PresShell::ReleaseCapturingContent();
3399 if (aEvent) {
3400 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
3401 SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent),
3402 SnapToTickMarks::Yes);
3404 mIsDraggingRange = false;
3405 FireChangeEventIfNeeded();
3408 void HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent) {
3409 MOZ_ASSERT(mIsDraggingRange);
3411 mIsDraggingRange = false;
3412 if (PresShell::GetCapturingContent() == this) {
3413 PresShell::ReleaseCapturingContent();
3415 if (aIsForUserEvent) {
3416 SetValueOfRangeForUserEvent(mRangeThumbDragStartValue,
3417 SnapToTickMarks::Yes);
3418 } else {
3419 // Don't dispatch an 'input' event - at least not using
3420 // DispatchTrustedEvent.
3421 // TODO: decide what we should do here - bug 851782.
3422 nsAutoString val;
3423 mInputType->ConvertNumberToString(mRangeThumbDragStartValue, val);
3424 // TODO: What should we do if SetValueInternal fails? (The allocation
3425 // is small, so we should be fine here.)
3426 SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
3427 ValueSetterOption::SetValueChanged});
3428 if (nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame())) {
3429 frame->UpdateForValueChange();
3431 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3432 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3433 "Failed to dispatch input event");
3437 void HTMLInputElement::SetValueOfRangeForUserEvent(
3438 Decimal aValue, SnapToTickMarks aSnapToTickMarks) {
3439 MOZ_ASSERT(aValue.isFinite());
3440 if (aSnapToTickMarks == SnapToTickMarks::Yes) {
3441 MaybeSnapToTickMark(aValue);
3444 Decimal oldValue = GetValueAsDecimal();
3446 nsAutoString val;
3447 mInputType->ConvertNumberToString(aValue, val);
3448 // TODO: What should we do if SetValueInternal fails? (The allocation
3449 // is small, so we should be fine here.)
3450 SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
3451 ValueSetterOption::SetValueChanged});
3452 if (nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame())) {
3453 frame->UpdateForValueChange();
3456 if (GetValueAsDecimal() != oldValue) {
3457 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3458 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3459 "Failed to dispatch input event");
3463 void HTMLInputElement::StartNumberControlSpinnerSpin() {
3464 MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
3466 mNumberControlSpinnerIsSpinning = true;
3468 nsRepeatService::GetInstance()->Start(
3469 HandleNumberControlSpin, this, OwnerDoc(), "HandleNumberControlSpin"_ns);
3471 // Capture the mouse so that we can tell if the pointer moves from one
3472 // spin button to the other, or to some other element:
3473 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
3475 nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
3476 if (numberControlFrame) {
3477 numberControlFrame->SpinnerStateChanged();
3481 void HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState) {
3482 if (mNumberControlSpinnerIsSpinning) {
3483 if (PresShell::GetCapturingContent() == this) {
3484 PresShell::ReleaseCapturingContent();
3487 nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
3489 mNumberControlSpinnerIsSpinning = false;
3491 if (aState == eAllowDispatchingEvents) {
3492 FireChangeEventIfNeeded();
3495 nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
3496 if (numberControlFrame) {
3497 MOZ_ASSERT(aState == eAllowDispatchingEvents,
3498 "Shouldn't have primary frame for the element when we're not "
3499 "allowed to dispatch events to it anymore.");
3500 numberControlFrame->SpinnerStateChanged();
3505 void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
3506 // We can't use GetValidityState here because the validity state is not set
3507 // if the user hasn't previously taken an action to set or change the value,
3508 // according to the specs.
3509 if (HasBadInput()) {
3510 // If the user has typed a value into the control and inadvertently made a
3511 // mistake (e.g. put a thousand separator at the wrong point) we do not
3512 // want to wipe out what they typed if they try to increment/decrement the
3513 // value. Better is to highlight the value as being invalid so that they
3514 // can correct what they typed.
3515 // We only do this if there actually is a value typed in by/displayed to
3516 // the user. (IsValid() can return false if the 'required' attribute is
3517 // set and the value is the empty string.)
3518 if (!IsValueEmpty()) {
3519 // We pass 'true' for SetUserInteracted because we need the UI to update
3520 // _now_ or the user will wonder why the step behavior isn't functioning.
3521 SetUserInteracted(true);
3522 return;
3526 Decimal newValue = Decimal::nan(); // unchanged if value will not change
3528 nsresult rv = GetValueIfStepped(aDirection, CALLED_FOR_USER_EVENT, &newValue);
3530 if (NS_FAILED(rv) || !newValue.isFinite()) {
3531 return; // value should not or will not change
3534 nsAutoString newVal;
3535 mInputType->ConvertNumberToString(newValue, newVal);
3536 // TODO: What should we do if SetValueInternal fails? (The allocation
3537 // is small, so we should be fine here.)
3538 SetValueInternal(newVal, {ValueSetterOption::BySetUserInputAPI,
3539 ValueSetterOption::SetValueChanged});
3542 static bool SelectTextFieldOnFocus() {
3543 if (!gSelectTextFieldOnFocus) {
3544 int32_t selectTextfieldsOnKeyFocus = -1;
3545 nsresult rv =
3546 LookAndFeel::GetInt(LookAndFeel::IntID::SelectTextfieldsOnKeyFocus,
3547 &selectTextfieldsOnKeyFocus);
3548 if (NS_FAILED(rv)) {
3549 gSelectTextFieldOnFocus = -1;
3550 } else {
3551 gSelectTextFieldOnFocus = selectTextfieldsOnKeyFocus != 0 ? 1 : -1;
3555 return gSelectTextFieldOnFocus == 1;
3558 bool HTMLInputElement::ShouldPreventDOMActivateDispatch(
3559 EventTarget* aOriginalTarget) {
3561 * For the moment, there is only one situation where we actually want to
3562 * prevent firing a DOMActivate event:
3563 * - we are a <input type='file'> that just got a click event,
3564 * - the event was targeted to our button which should have sent a
3565 * DOMActivate event.
3568 if (mType != FormControlType::InputFile) {
3569 return false;
3572 Element* target = Element::FromEventTargetOrNull(aOriginalTarget);
3573 if (!target) {
3574 return false;
3577 return target->GetParent() == this &&
3578 target->IsRootOfNativeAnonymousSubtree() &&
3579 target->IsHTMLElement(nsGkAtoms::button);
3582 nsresult HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor) {
3583 // Open a file picker when we receive a click on a <input type='file'>, or
3584 // open a color picker when we receive a click on a <input type='color'>.
3585 // A click is handled if it's the left mouse button.
3586 // We do not prevent non-trusted click because authors can already use
3587 // .click(). However, the pickers will follow the rules of popup-blocking.
3588 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3589 if (!(mouseEvent && mouseEvent->IsLeftClickEvent())) {
3590 return NS_OK;
3592 if (mType == FormControlType::InputFile) {
3593 // If the user clicked on the "Choose folder..." button we open the
3594 // directory picker, else we open the file picker.
3595 FilePickerType type = FILE_PICKER_FILE;
3596 nsIContent* target =
3597 nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
3598 if (target && target->FindFirstNonChromeOnlyAccessContent() == this &&
3599 StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
3600 HasAttr(nsGkAtoms::webkitdirectory)) {
3601 type = FILE_PICKER_DIRECTORY;
3603 return InitFilePicker(type);
3605 if (mType == FormControlType::InputColor) {
3606 return InitColorPicker();
3609 return NS_OK;
3613 * Return true if the input event should be ignored because of its modifiers.
3614 * Control is treated specially, since sometimes we ignore it, and sometimes
3615 * we don't (for webcompat reasons).
3617 static bool IgnoreInputEventWithModifier(const WidgetInputEvent& aEvent,
3618 bool ignoreControl) {
3619 return (ignoreControl && aEvent.IsControl()) ||
3620 aEvent.IsAltGraph()
3621 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
3622 // Meta key is the Windows Logo key on Windows and Linux which may
3623 // assign some special meaning for the events while it's pressed.
3624 // On the other hand, it's a normal modifier in macOS and Android.
3625 // Therefore, We should ignore it only in Win/Linux.
3626 || aEvent.IsMeta()
3627 #endif
3628 || aEvent.IsFn();
3631 bool HTMLInputElement::StepsInputValue(
3632 const WidgetKeyboardEvent& aEvent) const {
3633 if (mType != FormControlType::InputNumber) {
3634 return false;
3636 if (aEvent.mMessage != eKeyPress) {
3637 return false;
3639 if (!aEvent.IsTrusted()) {
3640 return false;
3642 if (aEvent.mKeyCode != NS_VK_UP && aEvent.mKeyCode != NS_VK_DOWN) {
3643 return false;
3645 if (IgnoreInputEventWithModifier(aEvent, false)) {
3646 return false;
3648 if (aEvent.DefaultPrevented()) {
3649 return false;
3651 if (!IsMutable()) {
3652 return false;
3654 return true;
3657 static bool ActivatesWithKeyboard(FormControlType aType, uint32_t aKeyCode) {
3658 switch (aType) {
3659 case FormControlType::InputCheckbox:
3660 case FormControlType::InputRadio:
3661 // Checkbox and Radio try to submit on Enter press
3662 return aKeyCode != NS_VK_RETURN;
3663 case FormControlType::InputButton:
3664 case FormControlType::InputReset:
3665 case FormControlType::InputSubmit:
3666 case FormControlType::InputFile:
3667 case FormControlType::InputImage: // Bug 34418
3668 case FormControlType::InputColor:
3669 return true;
3670 default:
3671 return false;
3675 nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
3676 if (aVisitor.mEvent->mMessage == eBlur) {
3677 if (mIsDraggingRange) {
3678 FinishRangeThumbDrag();
3679 } else if (mNumberControlSpinnerIsSpinning) {
3680 StopNumberControlSpinnerSpin();
3684 nsresult rv = NS_OK;
3685 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
3687 // Ideally we would make the default action for click and space just dispatch
3688 // DOMActivate, and the default action for DOMActivate flip the checkbox/
3689 // radio state and fire onchange. However, for backwards compatibility, we
3690 // need to flip the state before firing click, and we need to fire click
3691 // when space is pressed. So, we just nest the firing of DOMActivate inside
3692 // the click event handling, and allow cancellation of DOMActivate to cancel
3693 // the click.
3694 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault &&
3695 !IsSingleLineTextControl(true) && mType != FormControlType::InputNumber) {
3696 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3697 if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
3698 OwnerDoc()->MayHaveDOMActivateListeners() &&
3699 !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->mOriginalTarget)) {
3700 // DOMActive event should be trusted since the activation is actually
3701 // occurred even if the cause is an untrusted click event.
3702 InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
3703 actEvent.mDetail = 1;
3705 if (RefPtr<PresShell> presShell =
3706 aVisitor.mPresContext ? aVisitor.mPresContext->GetPresShell()
3707 : nullptr) {
3708 nsEventStatus status = nsEventStatus_eIgnore;
3709 mInInternalActivate = true;
3710 rv = presShell->HandleDOMEventWithTarget(this, &actEvent, &status);
3711 mInInternalActivate = false;
3713 // If activate is cancelled, we must do the same as when click is
3714 // cancelled (revert the checkbox to its original value).
3715 if (status == nsEventStatus_eConsumeNoDefault) {
3716 aVisitor.mEventStatus = status;
3722 bool preventDefault =
3723 aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault;
3724 if (IsDisabled() && oldType != FormControlType::InputCheckbox &&
3725 oldType != FormControlType::InputRadio) {
3726 // Behave as if defaultPrevented when the element becomes disabled by event
3727 // listeners. Checkboxes and radio buttons should still process clicks for
3728 // web compat. See:
3729 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
3730 preventDefault = true;
3733 if (NS_SUCCEEDED(rv)) {
3734 WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
3735 if (keyEvent && StepsInputValue(*keyEvent)) {
3736 StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
3737 FireChangeEventIfNeeded();
3738 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3739 } else if (!preventDefault) {
3740 if (keyEvent && ActivatesWithKeyboard(mType, keyEvent->mKeyCode) &&
3741 keyEvent->IsTrusted()) {
3742 // We maybe dispatch a synthesized click for keyboard activation.
3743 HandleKeyboardActivation(aVisitor);
3746 switch (aVisitor.mEvent->mMessage) {
3747 case eFocus: {
3748 // see if we should select the contents of the textbox. This happens
3749 // for text and password fields when the field was focused by the
3750 // keyboard or a navigation, the platform allows it, and it wasn't
3751 // just because we raised a window.
3753 // While it'd usually make sense, we don't do this for JS callers
3754 // because it causes some compat issues, see bug 1712724 for example.
3755 nsFocusManager* fm = nsFocusManager::GetFocusManager();
3756 if (fm && IsSingleLineTextControl(false) &&
3757 !aVisitor.mEvent->AsFocusEvent()->mFromRaise &&
3758 SelectTextFieldOnFocus()) {
3759 if (Document* document = GetComposedDoc()) {
3760 uint32_t lastFocusMethod =
3761 fm->GetLastFocusMethod(document->GetWindow());
3762 const bool shouldSelectAllOnFocus = [&] {
3763 if (lastFocusMethod & nsIFocusManager::FLAG_BYMOVEFOCUS) {
3764 return true;
3766 if (lastFocusMethod & nsIFocusManager::FLAG_BYJS) {
3767 return false;
3769 return bool(lastFocusMethod & nsIFocusManager::FLAG_BYKEY);
3770 }();
3771 if (shouldSelectAllOnFocus) {
3772 RefPtr<nsPresContext> presContext =
3773 GetPresContext(eForComposedDoc);
3774 SelectAll(presContext);
3778 break;
3781 case eKeyDown: {
3782 // For compatibility with the other browsers, we should active this
3783 // element at least when a checkbox or a radio button.
3784 // TODO: Investigate which elements are activated by space key in the
3785 // other browsers.
3786 if (aVisitor.mPresContext && keyEvent->IsTrusted() && !IsDisabled() &&
3787 keyEvent->ShouldWorkAsSpaceKey() &&
3788 (mType == FormControlType::InputCheckbox ||
3789 mType == FormControlType::InputRadio)) {
3790 EventStateManager::SetActiveManager(
3791 aVisitor.mPresContext->EventStateManager(), this);
3793 break;
3796 case eKeyPress: {
3797 if (mType == FormControlType::InputRadio && keyEvent->IsTrusted() &&
3798 !keyEvent->IsAlt() && !keyEvent->IsControl() &&
3799 !keyEvent->IsMeta()) {
3800 rv = MaybeHandleRadioButtonNavigation(aVisitor, keyEvent->mKeyCode);
3804 * For some input types, if the user hits enter, the form is
3805 * submitted.
3807 * Bug 99920, bug 109463 and bug 147850:
3808 * (a) if there is a submit control in the form, click the first
3809 * submit control in the form.
3810 * (b) if there is just one text control in the form, submit by
3811 * sending a submit event directly to the form
3812 * (c) if there is more than one text input and no submit buttons, do
3813 * not submit, period.
3816 if (keyEvent->mKeyCode == NS_VK_RETURN && keyEvent->IsTrusted() &&
3817 (IsSingleLineTextControl(false, mType) ||
3818 IsDateTimeInputType(mType) ||
3819 mType == FormControlType::InputCheckbox ||
3820 mType == FormControlType::InputRadio)) {
3821 if (IsSingleLineTextControl(false, mType) ||
3822 IsDateTimeInputType(mType)) {
3823 FireChangeEventIfNeeded();
3826 if (aVisitor.mPresContext) {
3827 MaybeSubmitForm(aVisitor.mPresContext);
3831 if (mType == FormControlType::InputRange && keyEvent->IsTrusted() &&
3832 !keyEvent->IsAlt() && !keyEvent->IsControl() &&
3833 !keyEvent->IsMeta() &&
3834 (keyEvent->mKeyCode == NS_VK_LEFT ||
3835 keyEvent->mKeyCode == NS_VK_RIGHT ||
3836 keyEvent->mKeyCode == NS_VK_UP ||
3837 keyEvent->mKeyCode == NS_VK_DOWN ||
3838 keyEvent->mKeyCode == NS_VK_PAGE_UP ||
3839 keyEvent->mKeyCode == NS_VK_PAGE_DOWN ||
3840 keyEvent->mKeyCode == NS_VK_HOME ||
3841 keyEvent->mKeyCode == NS_VK_END)) {
3842 Decimal minimum = GetMinimum();
3843 Decimal maximum = GetMaximum();
3844 MOZ_ASSERT(minimum.isFinite() && maximum.isFinite());
3845 if (minimum < maximum) { // else the value is locked to the minimum
3846 Decimal value = GetValueAsDecimal();
3847 Decimal step = GetStep();
3848 if (step == kStepAny) {
3849 step = GetDefaultStep();
3851 MOZ_ASSERT(value.isFinite() && step.isFinite());
3852 Decimal newValue;
3853 switch (keyEvent->mKeyCode) {
3854 case NS_VK_LEFT:
3855 newValue = value +
3856 (GetComputedDirectionality() == Directionality::Rtl
3857 ? step
3858 : -step);
3859 break;
3860 case NS_VK_RIGHT:
3861 newValue = value +
3862 (GetComputedDirectionality() == Directionality::Rtl
3863 ? -step
3864 : step);
3865 break;
3866 case NS_VK_UP:
3867 // Even for horizontal range, "up" means "increase"
3868 newValue = value + step;
3869 break;
3870 case NS_VK_DOWN:
3871 // Even for horizontal range, "down" means "decrease"
3872 newValue = value - step;
3873 break;
3874 case NS_VK_HOME:
3875 newValue = minimum;
3876 break;
3877 case NS_VK_END:
3878 newValue = maximum;
3879 break;
3880 case NS_VK_PAGE_UP:
3881 // For PgUp/PgDn we jump 10% of the total range, unless step
3882 // requires us to jump more.
3883 newValue =
3884 value + std::max(step, (maximum - minimum) / Decimal(10));
3885 break;
3886 case NS_VK_PAGE_DOWN:
3887 newValue =
3888 value - std::max(step, (maximum - minimum) / Decimal(10));
3889 break;
3891 SetValueOfRangeForUserEvent(newValue);
3892 FireChangeEventIfNeeded();
3893 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3897 } break; // eKeyPress
3899 case eMouseDown:
3900 case eMouseUp:
3901 case eMouseDoubleClick: {
3902 // cancel all of these events for buttons
3903 // XXXsmaug Why?
3904 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3905 if (mouseEvent->mButton == MouseButton::eMiddle ||
3906 mouseEvent->mButton == MouseButton::eSecondary) {
3907 if (mType == FormControlType::InputButton ||
3908 mType == FormControlType::InputReset ||
3909 mType == FormControlType::InputSubmit) {
3910 if (aVisitor.mDOMEvent) {
3911 aVisitor.mDOMEvent->StopPropagation();
3912 } else {
3913 rv = NS_ERROR_FAILURE;
3917 if (mType == FormControlType::InputNumber &&
3918 aVisitor.mEvent->IsTrusted()) {
3919 if (mouseEvent->mButton == MouseButton::ePrimary &&
3920 !IgnoreInputEventWithModifier(*mouseEvent, false)) {
3921 nsNumberControlFrame* numberControlFrame =
3922 do_QueryFrame(GetPrimaryFrame());
3923 if (numberControlFrame) {
3924 if (aVisitor.mEvent->mMessage == eMouseDown && IsMutable()) {
3925 switch (numberControlFrame->GetSpinButtonForPointerEvent(
3926 aVisitor.mEvent->AsMouseEvent())) {
3927 case nsNumberControlFrame::eSpinButtonUp:
3928 StepNumberControlForUserEvent(1);
3929 mNumberControlSpinnerSpinsUp = true;
3930 StartNumberControlSpinnerSpin();
3931 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3932 break;
3933 case nsNumberControlFrame::eSpinButtonDown:
3934 StepNumberControlForUserEvent(-1);
3935 mNumberControlSpinnerSpinsUp = false;
3936 StartNumberControlSpinnerSpin();
3937 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3938 break;
3943 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
3944 // We didn't handle this to step up/down. Whatever this was, be
3945 // aggressive about stopping the spin. (And don't set
3946 // nsEventStatus_eConsumeNoDefault after doing so, since that
3947 // might prevent, say, the context menu from opening.)
3948 StopNumberControlSpinnerSpin();
3951 break;
3953 #if !defined(ANDROID) && !defined(XP_MACOSX)
3954 case eWheel: {
3955 // Handle wheel events as increasing / decreasing the input element's
3956 // value when it's focused and it's type is number or range.
3957 WidgetWheelEvent* wheelEvent = aVisitor.mEvent->AsWheelEvent();
3958 if (!aVisitor.mEvent->DefaultPrevented() &&
3959 aVisitor.mEvent->IsTrusted() && IsMutable() && wheelEvent &&
3960 wheelEvent->mDeltaY != 0 &&
3961 wheelEvent->mDeltaMode != WheelEvent_Binding::DOM_DELTA_PIXEL) {
3962 if (mType == FormControlType::InputNumber) {
3963 if (nsContentUtils::IsFocusedContent(this)) {
3964 StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
3965 FireChangeEventIfNeeded();
3966 aVisitor.mEvent->PreventDefault();
3968 } else if (mType == FormControlType::InputRange &&
3969 nsContentUtils::IsFocusedContent(this) &&
3970 GetMinimum() < GetMaximum()) {
3971 Decimal value = GetValueAsDecimal();
3972 Decimal step = GetStep();
3973 if (step == kStepAny) {
3974 step = GetDefaultStep();
3976 MOZ_ASSERT(value.isFinite() && step.isFinite());
3977 SetValueOfRangeForUserEvent(
3978 wheelEvent->mDeltaY < 0 ? value + step : value - step);
3979 FireChangeEventIfNeeded();
3980 aVisitor.mEvent->PreventDefault();
3983 break;
3985 #endif
3986 case eMouseClick: {
3987 if (!aVisitor.mEvent->DefaultPrevented() &&
3988 aVisitor.mEvent->IsTrusted() &&
3989 aVisitor.mEvent->AsMouseEvent()->mButton ==
3990 MouseButton::ePrimary) {
3991 // TODO(emilio): Handling this should ideally not move focus.
3992 if (mType == FormControlType::InputSearch) {
3993 if (nsSearchControlFrame* searchControlFrame =
3994 do_QueryFrame(GetPrimaryFrame())) {
3995 Element* clearButton = searchControlFrame->GetAnonClearButton();
3996 if (clearButton &&
3997 aVisitor.mEvent->mOriginalTarget == clearButton) {
3998 SetUserInput(EmptyString(),
3999 *nsContentUtils::GetSystemPrincipal());
4000 // TODO(emilio): This should focus the input, but calling
4001 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
4002 // an inconsistent state where we're focused but don't match
4003 // :focus-visible / :focus.
4006 } else if (mType == FormControlType::InputPassword) {
4007 if (nsTextControlFrame* textControlFrame =
4008 do_QueryFrame(GetPrimaryFrame())) {
4009 auto* reveal = textControlFrame->GetRevealButton();
4010 if (reveal && aVisitor.mEvent->mOriginalTarget == reveal) {
4011 SetRevealPassword(!RevealPassword());
4012 // TODO(emilio): This should focus the input, but calling
4013 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
4014 // an inconsistent state where we're focused but don't match
4015 // :focus-visible / :focus.
4020 break;
4022 default:
4023 break;
4026 // Bug 1459231: Temporarily needed till links respect activation target,
4027 // then also remove NS_OUTER_ACTIVATE_EVENT. The appropriate
4028 // behavior/model for links is still under discussion (see
4029 // https://github.com/whatwg/html/issues/1576). For now, we aim for
4030 // consistency with other browsers.
4031 if (aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT) {
4032 switch (mType) {
4033 case FormControlType::InputReset:
4034 case FormControlType::InputSubmit:
4035 case FormControlType::InputImage:
4036 if (mForm) {
4037 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4039 break;
4040 case FormControlType::InputCheckbox:
4041 case FormControlType::InputRadio:
4042 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4043 break;
4044 default:
4045 break;
4049 } // if
4051 if (NS_SUCCEEDED(rv) && mType == FormControlType::InputRange) {
4052 PostHandleEventForRangeThumb(aVisitor);
4055 if (!preventDefault) {
4056 MOZ_TRY(MaybeInitPickers(aVisitor));
4058 return NS_OK;
4061 void EndSubmitClick(EventChainPostVisitor& aVisitor) {
4062 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
4063 if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) &&
4064 (oldType == FormControlType::InputSubmit ||
4065 oldType == FormControlType::InputImage)) {
4066 nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mItemData));
4067 RefPtr<HTMLFormElement> form = HTMLFormElement::FromNodeOrNull(content);
4068 // Tell the form that we are about to exit a click handler,
4069 // so the form knows not to defer subsequent submissions.
4070 // The pending ones that were created during the handler
4071 // will be flushed or forgotten.
4072 form->OnSubmitClickEnd();
4073 // tell the form to flush a possible pending submission.
4074 // the reason is that the script returned false (the event was
4075 // not ignored) so if there is a stored submission, it needs to
4076 // be submitted immediately.
4077 form->FlushPendingSubmission();
4081 void HTMLInputElement::ActivationBehavior(EventChainPostVisitor& aVisitor) {
4082 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
4084 if (IsDisabled() && oldType != FormControlType::InputCheckbox &&
4085 oldType != FormControlType::InputRadio) {
4086 // Behave as if defaultPrevented when the element becomes disabled by event
4087 // listeners. Checkboxes and radio buttons should still process clicks for
4088 // web compat. See:
4089 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
4090 EndSubmitClick(aVisitor);
4091 return;
4094 if (mCheckedIsToggled) {
4095 SetUserInteracted(true);
4097 // Fire input event and then change event.
4098 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
4099 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
4100 "Failed to dispatch input event");
4102 // FIXME: Why is this different than every other change event?
4103 nsContentUtils::DispatchTrustedEvent<WidgetEvent>(
4104 OwnerDoc(), static_cast<Element*>(this), eFormChange, CanBubble::eYes,
4105 Cancelable::eNo);
4106 #ifdef ACCESSIBILITY
4107 // Fire an event to notify accessibility
4108 if (mType == FormControlType::InputCheckbox) {
4109 if (nsContentUtils::MayHaveFormCheckboxStateChangeListeners()) {
4110 FireEventForAccessibility(this, eFormCheckboxStateChange);
4112 } else if (nsContentUtils::MayHaveFormRadioStateChangeListeners()) {
4113 FireEventForAccessibility(this, eFormRadioStateChange);
4114 // Fire event for the previous selected radio.
4115 nsCOMPtr<nsIContent> content = do_QueryInterface(aVisitor.mItemData);
4116 if (auto* previous = HTMLInputElement::FromNodeOrNull(content)) {
4117 FireEventForAccessibility(previous, eFormRadioStateChange);
4120 #endif
4123 switch (mType) {
4124 case FormControlType::InputReset:
4125 case FormControlType::InputSubmit:
4126 case FormControlType::InputImage:
4127 if (mForm) {
4128 // Hold a strong ref while dispatching
4129 RefPtr<HTMLFormElement> form(mForm);
4130 if (mType == FormControlType::InputReset) {
4131 form->MaybeReset(this);
4132 } else {
4133 form->MaybeSubmit(this);
4135 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4137 break;
4139 default:
4140 break;
4141 } // switch
4142 if (IsButtonControl()) {
4143 if (!GetInvokeTargetElement()) {
4144 HandlePopoverTargetAction();
4145 } else {
4146 HandleInvokeTargetAction();
4150 EndSubmitClick(aVisitor);
4153 void HTMLInputElement::LegacyCanceledActivationBehavior(
4154 EventChainPostVisitor& aVisitor) {
4155 bool originalCheckedValue =
4156 !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
4157 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
4159 if (mCheckedIsToggled) {
4160 // if it was canceled and a radio button, then set the old
4161 // selected btn to TRUE. if it is a checkbox then set it to its
4162 // original value (legacy-canceled-activation)
4163 if (oldType == FormControlType::InputRadio) {
4164 nsCOMPtr<nsIContent> content = do_QueryInterface(aVisitor.mItemData);
4165 HTMLInputElement* selectedRadioButton =
4166 HTMLInputElement::FromNodeOrNull(content);
4167 if (selectedRadioButton) {
4168 selectedRadioButton->SetChecked(true);
4170 // If there was no checked radio button or this one is no longer a
4171 // radio button we must reset it back to false to cancel the action.
4172 // See how the web of hack grows?
4173 if (!selectedRadioButton || mType != FormControlType::InputRadio) {
4174 DoSetChecked(false, true, true);
4176 } else if (oldType == FormControlType::InputCheckbox) {
4177 bool originalIndeterminateValue =
4178 !!(aVisitor.mItemFlags & NS_ORIGINAL_INDETERMINATE_VALUE);
4179 SetIndeterminateInternal(originalIndeterminateValue, false);
4180 DoSetChecked(originalCheckedValue, true, true);
4184 // Relevant for bug 242494: submit button with "submit(); return false;"
4185 EndSubmitClick(aVisitor);
4188 enum class RadioButtonMove { Back, Forward, None };
4189 nsresult HTMLInputElement::MaybeHandleRadioButtonNavigation(
4190 EventChainPostVisitor& aVisitor, uint32_t aKeyCode) {
4191 auto move = [&] {
4192 switch (aKeyCode) {
4193 case NS_VK_UP:
4194 return RadioButtonMove::Back;
4195 case NS_VK_DOWN:
4196 return RadioButtonMove::Forward;
4197 case NS_VK_LEFT:
4198 case NS_VK_RIGHT: {
4199 const bool isRtl = GetComputedDirectionality() == Directionality::Rtl;
4200 return isRtl == (aKeyCode == NS_VK_LEFT) ? RadioButtonMove::Forward
4201 : RadioButtonMove::Back;
4204 return RadioButtonMove::None;
4205 }();
4206 if (move == RadioButtonMove::None) {
4207 return NS_OK;
4209 // Arrow key pressed, focus+select prev/next radio button
4210 RefPtr<HTMLInputElement> selectedRadioButton;
4211 if (auto* container = GetCurrentRadioGroupContainer()) {
4212 nsAutoString name;
4213 GetAttr(nsGkAtoms::name, name);
4214 container->GetNextRadioButton(name, move == RadioButtonMove::Back, this,
4215 getter_AddRefs(selectedRadioButton));
4217 if (!selectedRadioButton) {
4218 return NS_OK;
4220 FocusOptions options;
4221 ErrorResult error;
4222 selectedRadioButton->Focus(options, CallerType::System, error);
4223 if (error.Failed()) {
4224 return error.StealNSResult();
4226 nsresult rv = DispatchSimulatedClick(
4227 selectedRadioButton, aVisitor.mEvent->IsTrusted(), aVisitor.mPresContext);
4228 if (NS_SUCCEEDED(rv)) {
4229 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4231 return rv;
4234 void HTMLInputElement::PostHandleEventForRangeThumb(
4235 EventChainPostVisitor& aVisitor) {
4236 MOZ_ASSERT(mType == FormControlType::InputRange);
4238 if (nsEventStatus_eConsumeNoDefault == aVisitor.mEventStatus ||
4239 !(aVisitor.mEvent->mClass == eMouseEventClass ||
4240 aVisitor.mEvent->mClass == eTouchEventClass ||
4241 aVisitor.mEvent->mClass == eKeyboardEventClass)) {
4242 return;
4245 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4246 if (!rangeFrame && mIsDraggingRange) {
4247 CancelRangeThumbDrag();
4248 return;
4251 switch (aVisitor.mEvent->mMessage) {
4252 case eMouseDown:
4253 case eTouchStart: {
4254 if (mIsDraggingRange) {
4255 break;
4257 if (PresShell::GetCapturingContent()) {
4258 break; // don't start drag if someone else is already capturing
4260 WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
4261 if (IgnoreInputEventWithModifier(*inputEvent, true)) {
4262 break; // ignore
4264 if (aVisitor.mEvent->mMessage == eMouseDown) {
4265 if (aVisitor.mEvent->AsMouseEvent()->mButtons ==
4266 MouseButtonsFlag::ePrimaryFlag) {
4267 StartRangeThumbDrag(inputEvent);
4268 } else if (mIsDraggingRange) {
4269 CancelRangeThumbDrag();
4271 } else {
4272 if (aVisitor.mEvent->AsTouchEvent()->mTouches.Length() == 1) {
4273 StartRangeThumbDrag(inputEvent);
4274 } else if (mIsDraggingRange) {
4275 CancelRangeThumbDrag();
4278 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4279 } break;
4281 case eMouseMove:
4282 case eTouchMove:
4283 if (!mIsDraggingRange) {
4284 break;
4286 if (PresShell::GetCapturingContent() != this) {
4287 // Someone else grabbed capture.
4288 CancelRangeThumbDrag();
4289 break;
4291 SetValueOfRangeForUserEvent(
4292 rangeFrame->GetValueAtEventPoint(aVisitor.mEvent->AsInputEvent()),
4293 SnapToTickMarks::Yes);
4294 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4295 break;
4297 case eMouseUp:
4298 case eTouchEnd:
4299 if (!mIsDraggingRange) {
4300 break;
4302 // We don't check to see whether we are the capturing content here and
4303 // call CancelRangeThumbDrag() if that is the case. We just finish off
4304 // the drag and set our final value (unless someone has called
4305 // preventDefault() and prevents us getting here).
4306 FinishRangeThumbDrag(aVisitor.mEvent->AsInputEvent());
4307 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4308 break;
4310 case eKeyPress:
4311 if (mIsDraggingRange &&
4312 aVisitor.mEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
4313 CancelRangeThumbDrag();
4315 break;
4317 case eTouchCancel:
4318 if (mIsDraggingRange) {
4319 CancelRangeThumbDrag();
4321 break;
4323 default:
4324 break;
4328 void HTMLInputElement::MaybeLoadImage() {
4329 // Our base URI may have changed; claim that our URI changed, and the
4330 // nsImageLoadingContent will decide whether a new image load is warranted.
4331 nsAutoString uri;
4332 if (mType == FormControlType::InputImage && GetAttr(nsGkAtoms::src, uri) &&
4333 (NS_FAILED(LoadImage(uri, false, true, eImageLoadType_Normal,
4334 mSrcTriggeringPrincipal)) ||
4335 !LoadingEnabled())) {
4336 CancelImageRequests(true);
4340 nsresult HTMLInputElement::BindToTree(BindContext& aContext, nsINode& aParent) {
4341 // If we are currently bound to a disconnected subtree root, remove
4342 // ourselves from it first.
4343 if (!mForm && mType == FormControlType::InputRadio) {
4344 RemoveFromRadioGroup();
4347 nsresult rv =
4348 nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent);
4349 NS_ENSURE_SUCCESS(rv, rv);
4351 nsImageLoadingContent::BindToTree(aContext, aParent);
4353 if (mType == FormControlType::InputImage) {
4354 // Our base URI may have changed; claim that our URI changed, and the
4355 // nsImageLoadingContent will decide whether a new image load is warranted.
4356 if (HasAttr(nsGkAtoms::src)) {
4357 // Mark channel as urgent-start before load image if the image load is
4358 // initaiated by a user interaction.
4359 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
4361 nsContentUtils::AddScriptRunner(
4362 NewRunnableMethod("dom::HTMLInputElement::MaybeLoadImage", this,
4363 &HTMLInputElement::MaybeLoadImage));
4367 // Add radio to document if we don't have a form already (if we do it's
4368 // already been added into that group)
4369 if (!mForm && mType == FormControlType::InputRadio) {
4370 AddToRadioGroup();
4373 // Set direction based on value if dir=auto
4374 if (HasDirAuto()) {
4375 SetAutoDirectionality(false);
4378 // An element can't suffer from value missing if it is not in a document.
4379 // We have to check if we suffer from that as we are now in a document.
4380 UpdateValueMissingValidityState();
4382 // If there is a disabled fieldset in the parent chain, the element is now
4383 // barred from constraint validation and can't suffer from value missing
4384 // (call done before).
4385 UpdateBarredFromConstraintValidation();
4387 // And now make sure our state is up to date
4388 UpdateValidityElementStates(true);
4390 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4391 // Construct Shadow Root so web content can be hidden in the DOM.
4392 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes, DelegatesFocus::Yes);
4395 MaybeDispatchLoginManagerEvents(mForm);
4397 return rv;
4400 void HTMLInputElement::MaybeDispatchLoginManagerEvents(HTMLFormElement* aForm) {
4401 // Don't disptach the event if the <input> is disconnected
4402 // or belongs to a disconnected form
4403 if (!IsInComposedDoc()) {
4404 return;
4407 nsString eventType;
4408 Element* target = nullptr;
4410 if (mType == FormControlType::InputPassword) {
4411 // Don't fire another event if we have a pending event.
4412 if (aForm && aForm->mHasPendingPasswordEvent) {
4413 return;
4416 // TODO(Bug 1864404): Use one event for formless and form inputs.
4417 eventType = aForm ? u"DOMFormHasPassword"_ns : u"DOMInputPasswordAdded"_ns;
4419 target = aForm ? static_cast<Element*>(aForm) : this;
4421 if (aForm) {
4422 aForm->mHasPendingPasswordEvent = true;
4425 } else if (mType == FormControlType::InputEmail ||
4426 mType == FormControlType::InputText) {
4427 // Don't fire a username event if:
4428 // - <input> is not part of a form
4429 // - we have a pending event
4430 // - username only forms are not supported
4431 if (!aForm || aForm->mHasPendingPossibleUsernameEvent ||
4432 !StaticPrefs::signon_usernameOnlyForm_enabled()) {
4433 return;
4436 eventType = u"DOMFormHasPossibleUsername"_ns;
4437 target = aForm;
4439 aForm->mHasPendingPossibleUsernameEvent = true;
4441 } else {
4442 return;
4445 RefPtr<AsyncEventDispatcher> dispatcher = new AsyncEventDispatcher(
4446 target, eventType, CanBubble::eYes, ChromeOnlyDispatch::eYes);
4447 dispatcher->PostDOMEvent();
4450 void HTMLInputElement::UnbindFromTree(UnbindContext& aContext) {
4451 if (mType == FormControlType::InputPassword) {
4452 MaybeFireInputPasswordRemoved();
4455 // If we have a form and are unbound from it,
4456 // nsGenericHTMLFormControlElementWithState::UnbindFromTree() will unset the
4457 // form and that takes care of form's WillRemove so we just have to take care
4458 // of the case where we're removing from the document and we don't
4459 // have a form
4460 if (!mForm && mType == FormControlType::InputRadio) {
4461 RemoveFromRadioGroup();
4464 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4465 NotifyUAWidgetTeardown();
4468 nsImageLoadingContent::UnbindFromTree();
4469 nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext);
4471 // If we are contained within a disconnected subtree, attempt to add
4472 // ourselves to the subtree root's radio group.
4473 if (!mForm && mType == FormControlType::InputRadio) {
4474 AddToRadioGroup();
4477 // GetCurrentDoc is returning nullptr so we can update the value
4478 // missing validity state to reflect we are no longer into a doc.
4479 UpdateValueMissingValidityState();
4480 // We might be no longer disabled because of parent chain changed.
4481 UpdateBarredFromConstraintValidation();
4482 // And now make sure our state is up to date
4483 UpdateValidityElementStates(false);
4487 * @param aType InputElementTypes
4488 * @return true, iff SetRangeText applies to aType as specified at
4489 * https://html.spec.whatwg.org/#concept-input-apply.
4491 static bool SetRangeTextApplies(FormControlType aType) {
4492 return aType == FormControlType::InputText ||
4493 aType == FormControlType::InputSearch ||
4494 aType == FormControlType::InputUrl ||
4495 aType == FormControlType::InputTel ||
4496 aType == FormControlType::InputPassword;
4499 void HTMLInputElement::HandleTypeChange(FormControlType aNewType,
4500 bool aNotify) {
4501 FormControlType oldType = mType;
4502 MOZ_ASSERT(oldType != aNewType);
4504 mHasBeenTypePassword =
4505 mHasBeenTypePassword || aNewType == FormControlType::InputPassword;
4507 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
4508 // Input element can represent very different kinds of UIs, and we may
4509 // need to flush styling even when focusing the already focused input
4510 // element.
4511 fm->NeedsFlushBeforeEventHandling(this);
4514 if (oldType == FormControlType::InputPassword &&
4515 State().HasState(ElementState::REVEALED)) {
4516 // Modify the state directly to avoid dispatching events.
4517 RemoveStates(ElementState::REVEALED, aNotify);
4520 if (aNewType == FormControlType::InputFile ||
4521 oldType == FormControlType::InputFile) {
4522 if (aNewType == FormControlType::InputFile) {
4523 mFileData.reset(new FileData());
4524 } else {
4525 mFileData->Unlink();
4526 mFileData = nullptr;
4530 if (oldType == FormControlType::InputRange && mIsDraggingRange) {
4531 CancelRangeThumbDrag(false);
4534 const ValueModeType oldValueMode = GetValueMode();
4535 nsAutoString oldValue;
4536 if (oldValueMode == VALUE_MODE_VALUE) {
4537 // Doesn't matter what caller type we pass here, since we know we're not a
4538 // file input anyway.
4539 GetValue(oldValue, CallerType::NonSystem);
4542 TextControlState::SelectionProperties sp;
4544 if (IsSingleLineTextControl(false) && mInputData.mState) {
4545 mInputData.mState->SyncUpSelectionPropertiesBeforeDestruction();
4546 sp = mInputData.mState->GetSelectionProperties();
4549 // We already have a copy of the value, lets free it and changes the type.
4550 FreeData();
4551 mType = aNewType;
4552 void* memory = mInputTypeMem;
4553 mInputType = InputType::Create(this, mType, memory);
4555 if (IsSingleLineTextControl()) {
4556 mInputData.mState = TextControlState::Construct(this);
4557 if (!sp.IsDefault()) {
4558 mInputData.mState->SetSelectionProperties(sp);
4562 // Whether placeholder applies might have changed.
4563 UpdatePlaceholderShownState();
4564 // Whether readonly applies might have changed.
4565 UpdateReadOnlyState(aNotify);
4566 UpdateCheckedState(aNotify);
4567 UpdateIndeterminateState(aNotify);
4568 const bool isDefault = IsRadioOrCheckbox()
4569 ? DefaultChecked()
4570 : (mForm && mForm->IsDefaultSubmitElement(this));
4571 SetStates(ElementState::DEFAULT, isDefault, aNotify);
4573 // https://html.spec.whatwg.org/#input-type-change
4574 switch (GetValueMode()) {
4575 case VALUE_MODE_DEFAULT:
4576 case VALUE_MODE_DEFAULT_ON:
4577 // 1. If the previous state of the element's type attribute put the value
4578 // IDL attribute in the value mode, and the element's value is not the
4579 // empty string, and the new state of the element's type attribute puts
4580 // the value IDL attribute in either the default mode or the default/on
4581 // mode, then set the element's value content attribute to the
4582 // element's value.
4583 if (oldValueMode == VALUE_MODE_VALUE && !oldValue.IsEmpty()) {
4584 SetAttr(kNameSpaceID_None, nsGkAtoms::value, oldValue, true);
4586 break;
4587 case VALUE_MODE_VALUE: {
4588 ValueSetterOptions options{ValueSetterOption::ByInternalAPI};
4589 if (!SetRangeTextApplies(oldType) && SetRangeTextApplies(mType)) {
4590 options +=
4591 ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward;
4593 if (oldValueMode != VALUE_MODE_VALUE) {
4594 // 2. Otherwise, if the previous state of the element's type attribute
4595 // put the value IDL attribute in any mode other than the value
4596 // mode, and the new state of the element's type attribute puts the
4597 // value IDL attribute in the value mode, then set the value of the
4598 // element to the value of the value content attribute, if there is
4599 // one, or the empty string otherwise, and then set the control's
4600 // dirty value flag to false.
4601 nsAutoString value;
4602 GetAttr(nsGkAtoms::value, value);
4603 SetValueInternal(value, options);
4604 SetValueChanged(false);
4605 } else if (mValueChanged) {
4606 // We're both in the "value" mode state, we need to make no change per
4607 // spec, but due to how we store the value internally we need to call
4608 // SetValueInternal, if our value had changed at all.
4609 // TODO: What should we do if SetValueInternal fails? (The allocation
4610 // may potentially be big, but most likely we've failed to allocate
4611 // before the type change.)
4612 SetValueInternal(oldValue, options);
4613 } else {
4614 // The value dirty flag is not set, so our value is based on our default
4615 // value. But our default value might be dependent on the type. Make
4616 // sure to set it so that state is consistent.
4617 SetDefaultValueAsValue();
4619 break;
4621 case VALUE_MODE_FILENAME:
4622 default:
4623 // 3. Otherwise, if the previous state of the element's type attribute
4624 // put the value IDL attribute in any mode other than the filename
4625 // mode, and the new state of the element's type attribute puts the
4626 // value IDL attribute in the filename mode, then set the value of the
4627 // element to the empty string.
4629 // Setting the attribute to the empty string is basically calling
4630 // ClearFiles, but there can't be any files.
4631 break;
4634 // Updating mFocusedValue in consequence:
4635 // If the new type fires a change event on blur, but the previous type
4636 // doesn't, we should set mFocusedValue to the current value.
4637 // Otherwise, if the new type doesn't fire a change event on blur, but the
4638 // previous type does, we should clear out mFocusedValue.
4639 if (MayFireChangeOnBlur(mType) && !MayFireChangeOnBlur(oldType)) {
4640 GetValue(mFocusedValue, CallerType::System);
4641 } else if (!IsSingleLineTextControl(false, mType) &&
4642 IsSingleLineTextControl(false, oldType)) {
4643 mFocusedValue.Truncate();
4646 // Update or clear our required states since we may have changed from a
4647 // required input type to a non-required input type or viceversa.
4648 if (DoesRequiredApply()) {
4649 const bool isRequired = HasAttr(nsGkAtoms::required);
4650 UpdateRequiredState(isRequired, aNotify);
4651 } else {
4652 RemoveStates(ElementState::REQUIRED_STATES, aNotify);
4655 UpdateHasRange(aNotify);
4657 // Update validity states, but not element state. We'll update
4658 // element state later, as part of this attribute change.
4659 UpdateAllValidityStatesButNotElementState();
4661 UpdateApzAwareFlag();
4663 UpdateBarredFromConstraintValidation();
4665 // Changing type may affect auto directionality, or non-auto directionality
4666 // because of the special-case for <input type=tel>, as specified in
4667 // https://html.spec.whatwg.org/multipage/dom.html#the-directionality
4668 if (HasDirAuto()) {
4669 const bool autoDirAssociated = IsAutoDirectionalityAssociated(mType);
4670 if (IsAutoDirectionalityAssociated(oldType) != autoDirAssociated) {
4671 SetAutoDirectionality(aNotify);
4673 } else if (oldType == FormControlType::InputTel ||
4674 mType == FormControlType::InputTel) {
4675 RecomputeDirectionality(this, aNotify);
4678 if (oldType == FormControlType::InputImage ||
4679 mType == FormControlType::InputImage) {
4680 if (oldType == FormControlType::InputImage) {
4681 // We're no longer an image input. Cancel our image requests, if we have
4682 // any.
4683 CancelImageRequests(aNotify);
4684 RemoveStates(ElementState::BROKEN, aNotify);
4685 } else {
4686 // We just got switched to be an image input; we should see whether we
4687 // have an image to load;
4688 bool hasSrc = false;
4689 if (aNotify) {
4690 nsAutoString src;
4691 if ((hasSrc = GetAttr(nsGkAtoms::src, src))) {
4692 // Mark channel as urgent-start before load image if the image load is
4693 // initiated by a user interaction.
4694 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
4696 LoadImage(src, false, aNotify, eImageLoadType_Normal,
4697 mSrcTriggeringPrincipal);
4699 } else {
4700 hasSrc = HasAttr(nsGkAtoms::src);
4702 if (!hasSrc) {
4703 AddStates(ElementState::BROKEN, aNotify);
4706 // We should update our mapped attribute mapping function.
4707 if (mAttrs.HasAttrs() && !mAttrs.IsPendingMappedAttributeEvaluation()) {
4708 mAttrs.InfallibleMarkAsPendingPresAttributeEvaluation();
4709 if (auto* doc = GetComposedDoc()) {
4710 doc->ScheduleForPresAttrEvaluation(this);
4715 MaybeDispatchLoginManagerEvents(mForm);
4717 if (IsInComposedDoc()) {
4718 if (CreatesDateTimeWidget(oldType)) {
4719 if (!CreatesDateTimeWidget()) {
4720 // Switch away from date/time type.
4721 NotifyUAWidgetTeardown();
4722 } else {
4723 // Switch between date and time.
4724 NotifyUAWidgetSetupOrChange();
4726 } else if (CreatesDateTimeWidget()) {
4727 // Switch to date/time type.
4728 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes, DelegatesFocus::Yes);
4730 // If we're becoming a text control and have focus, make sure to show focus
4731 // rings.
4732 if (State().HasState(ElementState::FOCUS) && IsSingleLineTextControl() &&
4733 !IsSingleLineTextControl(/* aExcludePassword = */ false, oldType)) {
4734 AddStates(ElementState::FOCUSRING);
4739 void HTMLInputElement::MaybeSnapToTickMark(Decimal& aValue) {
4740 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4741 if (!rangeFrame) {
4742 return;
4744 auto tickMark = rangeFrame->NearestTickMark(aValue);
4745 if (tickMark.isNaN()) {
4746 return;
4748 auto rangeFrameSize = CSSPixel::FromAppUnits(rangeFrame->GetSize());
4749 CSSCoord rangeTrackLength;
4750 if (rangeFrame->IsHorizontal()) {
4751 rangeTrackLength = rangeFrameSize.width;
4752 } else {
4753 rangeTrackLength = rangeFrameSize.height;
4755 auto stepBase = GetStepBase();
4756 auto distanceToTickMark =
4757 rangeTrackLength * float(rangeFrame->GetDoubleAsFractionOfRange(
4758 stepBase + (tickMark - aValue).abs()));
4759 const CSSCoord magnetEffectRange(
4760 StaticPrefs::dom_range_element_magnet_effect_threshold());
4761 if (distanceToTickMark <= magnetEffectRange) {
4762 aValue = tickMark;
4766 void HTMLInputElement::SanitizeValue(nsAString& aValue,
4767 SanitizationKind aKind) const {
4768 NS_ASSERTION(mDoneCreating, "The element creation should be finished!");
4770 switch (mType) {
4771 case FormControlType::InputText:
4772 case FormControlType::InputSearch:
4773 case FormControlType::InputTel:
4774 case FormControlType::InputPassword: {
4775 aValue.StripCRLF();
4776 } break;
4777 case FormControlType::InputEmail: {
4778 aValue.StripCRLF();
4779 aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
4780 aValue);
4782 if (Multiple() && !aValue.IsEmpty()) {
4783 nsAutoString oldValue(aValue);
4784 HTMLSplitOnSpacesTokenizer tokenizer(oldValue, ',');
4785 aValue.Truncate(0);
4786 aValue.Append(tokenizer.nextToken());
4787 while (tokenizer.hasMoreTokens() ||
4788 tokenizer.separatorAfterCurrentToken()) {
4789 aValue.Append(',');
4790 aValue.Append(tokenizer.nextToken());
4793 } break;
4794 case FormControlType::InputUrl: {
4795 aValue.StripCRLF();
4797 aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
4798 aValue);
4799 } break;
4800 case FormControlType::InputNumber: {
4801 if (aKind == SanitizationKind::ForValueSetter && !aValue.IsEmpty() &&
4802 (aValue.First() == '+' || aValue.Last() == '.')) {
4803 // A value with a leading plus or trailing dot should fail to parse.
4804 // However, the localized parser accepts this, and when we convert it
4805 // back to a Decimal, it disappears. So, we need to check first.
4807 // FIXME(emilio): Should we just use the unlocalized parser
4808 // (StringToDecimal) for the value setter? Other browsers don't seem to
4809 // allow setting localized strings there, and that way we don't need
4810 // this special-case.
4811 aValue.Truncate();
4812 return;
4815 InputType::StringToNumberResult result =
4816 mInputType->ConvertStringToNumber(aValue);
4817 if (!result.mResult.isFinite()) {
4818 aValue.Truncate();
4819 return;
4821 switch (aKind) {
4822 case SanitizationKind::ForValueGetter: {
4823 // If the default non-localized algorithm parses the value, then we're
4824 // done, don't un-localize it, to avoid precision loss, and to
4825 // preserve scientific notation as well for example.
4826 if (!result.mLocalized) {
4827 return;
4829 // For the <input type=number> value getter, we return the unlocalized
4830 // value if it doesn't parse as StringToDecimal, for compat with other
4831 // browsers.
4832 char buf[32];
4833 DebugOnly<bool> ok = result.mResult.toString(buf, ArrayLength(buf));
4834 aValue.AssignASCII(buf);
4835 MOZ_ASSERT(ok, "buf not big enough");
4836 break;
4838 case SanitizationKind::ForDisplay:
4839 case SanitizationKind::ForValueSetter: {
4840 // We localize as needed, but if both the localized and unlocalized
4841 // version parse with the generic parser, we just use the unlocalized
4842 // one, to preserve the input as much as possible.
4844 // FIXME(emilio, bug 1622808): Localization should ideally be more
4845 // input-preserving.
4846 nsString localizedValue;
4847 mInputType->ConvertNumberToString(result.mResult, localizedValue);
4848 if (!StringToDecimal(localizedValue).isFinite()) {
4849 aValue = std::move(localizedValue);
4851 break;
4854 break;
4856 case FormControlType::InputRange: {
4857 Decimal minimum = GetMinimum();
4858 Decimal maximum = GetMaximum();
4859 MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
4860 "type=range should have a default maximum/minimum");
4862 // We use this to avoid modifying the string unnecessarily, since that
4863 // may introduce rounding. This is set to true only if the value we
4864 // parse out from aValue needs to be sanitized.
4865 bool needSanitization = false;
4867 Decimal value = mInputType->ConvertStringToNumber(aValue).mResult;
4868 if (!value.isFinite()) {
4869 needSanitization = true;
4870 // Set value to midway between minimum and maximum.
4871 value = maximum <= minimum ? minimum
4872 : minimum + (maximum - minimum) / Decimal(2);
4873 } else if (value < minimum || maximum < minimum) {
4874 needSanitization = true;
4875 value = minimum;
4876 } else if (value > maximum) {
4877 needSanitization = true;
4878 value = maximum;
4881 Decimal step = GetStep();
4882 if (step != kStepAny) {
4883 Decimal stepBase = GetStepBase();
4884 // There could be rounding issues below when dealing with fractional
4885 // numbers, but let's ignore that until ECMAScript supplies us with a
4886 // decimal number type.
4887 Decimal deltaToStep = NS_floorModulo(value - stepBase, step);
4888 if (deltaToStep != Decimal(0)) {
4889 // "suffering from a step mismatch"
4890 // Round the element's value to the nearest number for which the
4891 // element would not suffer from a step mismatch, and which is
4892 // greater than or equal to the minimum, and, if the maximum is not
4893 // less than the minimum, which is less than or equal to the
4894 // maximum, if there is a number that matches these constraints:
4895 MOZ_ASSERT(deltaToStep > Decimal(0),
4896 "stepBelow/stepAbove will be wrong");
4897 Decimal stepBelow = value - deltaToStep;
4898 Decimal stepAbove = value - deltaToStep + step;
4899 Decimal halfStep = step / Decimal(2);
4900 bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
4901 bool stepAboveInRange = stepAbove >= minimum && stepAbove <= maximum;
4902 bool stepBelowInRange = stepBelow >= minimum && stepBelow <= maximum;
4904 if ((stepAboveIsClosest || !stepBelowInRange) && stepAboveInRange) {
4905 needSanitization = true;
4906 value = stepAbove;
4907 } else if ((!stepAboveIsClosest || !stepAboveInRange) &&
4908 stepBelowInRange) {
4909 needSanitization = true;
4910 value = stepBelow;
4915 if (needSanitization) {
4916 char buf[32];
4917 DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
4918 aValue.AssignASCII(buf);
4919 MOZ_ASSERT(ok, "buf not big enough");
4921 } break;
4922 case FormControlType::InputDate: {
4923 if (!aValue.IsEmpty() && !IsValidDate(aValue)) {
4924 aValue.Truncate();
4926 } break;
4927 case FormControlType::InputTime: {
4928 if (!aValue.IsEmpty() && !IsValidTime(aValue)) {
4929 aValue.Truncate();
4931 } break;
4932 case FormControlType::InputMonth: {
4933 if (!aValue.IsEmpty() && !IsValidMonth(aValue)) {
4934 aValue.Truncate();
4936 } break;
4937 case FormControlType::InputWeek: {
4938 if (!aValue.IsEmpty() && !IsValidWeek(aValue)) {
4939 aValue.Truncate();
4941 } break;
4942 case FormControlType::InputDatetimeLocal: {
4943 if (!aValue.IsEmpty() && !IsValidDateTimeLocal(aValue)) {
4944 aValue.Truncate();
4945 } else {
4946 NormalizeDateTimeLocal(aValue);
4948 } break;
4949 case FormControlType::InputColor: {
4950 if (IsValidSimpleColor(aValue)) {
4951 ToLowerCase(aValue);
4952 } else {
4953 // Set default (black) color, if aValue wasn't parsed correctly.
4954 aValue.AssignLiteral("#000000");
4956 } break;
4957 default:
4958 break;
4962 Maybe<nscolor> HTMLInputElement::ParseSimpleColor(const nsAString& aColor) {
4963 // Input color string should be 7 length (i.e. a string representing a valid
4964 // simple color)
4965 if (aColor.Length() != 7 || aColor.First() != '#') {
4966 return {};
4969 const nsAString& withoutHash = StringTail(aColor, 6);
4970 nscolor color;
4971 if (!NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
4972 return {};
4975 return Some(color);
4978 bool HTMLInputElement::IsValidSimpleColor(const nsAString& aValue) const {
4979 if (aValue.Length() != 7 || aValue.First() != '#') {
4980 return false;
4983 for (int i = 1; i < 7; ++i) {
4984 if (!IsAsciiDigit(aValue[i]) && !(aValue[i] >= 'a' && aValue[i] <= 'f') &&
4985 !(aValue[i] >= 'A' && aValue[i] <= 'F')) {
4986 return false;
4989 return true;
4992 bool HTMLInputElement::IsLeapYear(uint32_t aYear) const {
4993 if ((aYear % 4 == 0 && aYear % 100 != 0) || (aYear % 400 == 0)) {
4994 return true;
4996 return false;
4999 uint32_t HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth,
5000 uint32_t aDay, bool isoWeek) const {
5001 MOZ_ASSERT(1 <= aMonth && aMonth <= 12, "month is in 1..12");
5002 MOZ_ASSERT(1 <= aDay && aDay <= 31, "day is in 1..31");
5004 // Tomohiko Sakamoto algorithm.
5005 int monthTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
5006 aYear -= aMonth < 3;
5008 uint32_t day = (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
5009 monthTable[aMonth - 1] + aDay) %
5012 if (isoWeek) {
5013 return ((day + 6) % 7) + 1;
5016 return day;
5019 uint32_t HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const {
5020 int day = DayOfWeek(aYear, 1, 1, true); // January 1.
5021 // A year starting on Thursday or a leap year starting on Wednesday has 53
5022 // weeks. All other years have 52 weeks.
5023 return day == 4 || (day == 3 && IsLeapYear(aYear)) ? kMaximumWeekInYear
5024 : kMaximumWeekInYear - 1;
5027 bool HTMLInputElement::IsValidWeek(const nsAString& aValue) const {
5028 uint32_t year, week;
5029 return ParseWeek(aValue, &year, &week);
5032 bool HTMLInputElement::IsValidMonth(const nsAString& aValue) const {
5033 uint32_t year, month;
5034 return ParseMonth(aValue, &year, &month);
5037 bool HTMLInputElement::IsValidDate(const nsAString& aValue) const {
5038 uint32_t year, month, day;
5039 return ParseDate(aValue, &year, &month, &day);
5042 bool HTMLInputElement::IsValidDateTimeLocal(const nsAString& aValue) const {
5043 uint32_t year, month, day, time;
5044 return ParseDateTimeLocal(aValue, &year, &month, &day, &time);
5047 bool HTMLInputElement::ParseYear(const nsAString& aValue,
5048 uint32_t* aYear) const {
5049 if (aValue.Length() < 4) {
5050 return false;
5053 return DigitSubStringToNumber(aValue, 0, aValue.Length(), aYear) &&
5054 *aYear > 0;
5057 bool HTMLInputElement::ParseMonth(const nsAString& aValue, uint32_t* aYear,
5058 uint32_t* aMonth) const {
5059 // Parse the year, month values out a string formatted as 'yyyy-mm'.
5060 if (aValue.Length() < 7) {
5061 return false;
5064 uint32_t endOfYearOffset = aValue.Length() - 3;
5065 if (aValue[endOfYearOffset] != '-') {
5066 return false;
5069 const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
5070 if (!ParseYear(yearStr, aYear)) {
5071 return false;
5074 return DigitSubStringToNumber(aValue, endOfYearOffset + 1, 2, aMonth) &&
5075 *aMonth > 0 && *aMonth <= 12;
5078 bool HTMLInputElement::ParseWeek(const nsAString& aValue, uint32_t* aYear,
5079 uint32_t* aWeek) const {
5080 // Parse the year, month values out a string formatted as 'yyyy-Www'.
5081 if (aValue.Length() < 8) {
5082 return false;
5085 uint32_t endOfYearOffset = aValue.Length() - 4;
5086 if (aValue[endOfYearOffset] != '-') {
5087 return false;
5090 if (aValue[endOfYearOffset + 1] != 'W') {
5091 return false;
5094 const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
5095 if (!ParseYear(yearStr, aYear)) {
5096 return false;
5099 return DigitSubStringToNumber(aValue, endOfYearOffset + 2, 2, aWeek) &&
5100 *aWeek > 0 && *aWeek <= MaximumWeekInYear(*aYear);
5103 bool HTMLInputElement::ParseDate(const nsAString& aValue, uint32_t* aYear,
5104 uint32_t* aMonth, uint32_t* aDay) const {
5106 * Parse the year, month, day values out a date string formatted as
5107 * yyyy-mm-dd. -The year must be 4 or more digits long, and year > 0 -The
5108 * month must be exactly 2 digits long, and 01 <= month <= 12 -The day must be
5109 * exactly 2 digit long, and 01 <= day <= maxday Where maxday is the number of
5110 * days in the month 'month' and year 'year'
5112 if (aValue.Length() < 10) {
5113 return false;
5116 uint32_t endOfMonthOffset = aValue.Length() - 3;
5117 if (aValue[endOfMonthOffset] != '-') {
5118 return false;
5121 const nsAString& yearMonthStr = Substring(aValue, 0, endOfMonthOffset);
5122 if (!ParseMonth(yearMonthStr, aYear, aMonth)) {
5123 return false;
5126 return DigitSubStringToNumber(aValue, endOfMonthOffset + 1, 2, aDay) &&
5127 *aDay > 0 && *aDay <= NumberOfDaysInMonth(*aMonth, *aYear);
5130 bool HTMLInputElement::ParseDateTimeLocal(const nsAString& aValue,
5131 uint32_t* aYear, uint32_t* aMonth,
5132 uint32_t* aDay,
5133 uint32_t* aTime) const {
5134 // Parse the year, month, day and time values out a string formatted as
5135 // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
5136 // seconds can be 1 to 3 digits.
5137 // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
5138 // or 'yyyy-mm-dd hh:mm'.
5139 if (aValue.Length() < 16) {
5140 return false;
5143 int32_t sepIndex = aValue.FindChar('T');
5144 if (sepIndex == -1) {
5145 sepIndex = aValue.FindChar(' ');
5147 if (sepIndex == -1) {
5148 return false;
5152 const nsAString& dateStr = Substring(aValue, 0, sepIndex);
5153 if (!ParseDate(dateStr, aYear, aMonth, aDay)) {
5154 return false;
5157 const nsAString& timeStr =
5158 Substring(aValue, sepIndex + 1, aValue.Length() - sepIndex + 1);
5159 if (!ParseTime(timeStr, aTime)) {
5160 return false;
5163 return true;
5166 void HTMLInputElement::NormalizeDateTimeLocal(nsAString& aValue) const {
5167 if (aValue.IsEmpty()) {
5168 return;
5171 // Use 'T' as the separator between date string and time string.
5172 int32_t sepIndex = aValue.FindChar(' ');
5173 if (sepIndex != -1) {
5174 aValue.ReplaceLiteral(sepIndex, 1, u"T");
5175 } else {
5176 sepIndex = aValue.FindChar('T');
5179 // Time expressed as the shortest possible string, which is hh:mm.
5180 if ((aValue.Length() - sepIndex) == 6) {
5181 return;
5184 // Fractions of seconds part is optional, ommit it if it's 0.
5185 if ((aValue.Length() - sepIndex) > 9) {
5186 const uint32_t millisecSepIndex = sepIndex + 9;
5187 uint32_t milliseconds;
5188 if (!DigitSubStringToNumber(aValue, millisecSepIndex + 1,
5189 aValue.Length() - (millisecSepIndex + 1),
5190 &milliseconds)) {
5191 return;
5194 if (milliseconds != 0) {
5195 return;
5198 aValue.Cut(millisecSepIndex, aValue.Length() - millisecSepIndex);
5201 // Seconds part is optional, ommit it if it's 0.
5202 const uint32_t secondSepIndex = sepIndex + 6;
5203 uint32_t seconds;
5204 if (!DigitSubStringToNumber(aValue, secondSepIndex + 1,
5205 aValue.Length() - (secondSepIndex + 1),
5206 &seconds)) {
5207 return;
5210 if (seconds != 0) {
5211 return;
5214 aValue.Cut(secondSepIndex, aValue.Length() - secondSepIndex);
5217 double HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear,
5218 uint32_t aWeek) const {
5219 double days = JS::DayFromYear(aYear) + (aWeek - 1) * 7;
5220 uint32_t dayOneIsoWeekday = DayOfWeek(aYear, 1, 1, true);
5222 // If day one of that year is on/before Thursday, we should subtract the
5223 // days that belong to last year in our first week, otherwise, our first
5224 // days belong to last year's last week, and we should add those days
5225 // back.
5226 if (dayOneIsoWeekday <= 4) {
5227 days -= (dayOneIsoWeekday - 1);
5228 } else {
5229 days += (7 - dayOneIsoWeekday + 1);
5232 return days;
5235 uint32_t HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth,
5236 uint32_t aYear) const {
5238 * Returns the number of days in a month.
5239 * Months that are |longMonths| always have 31 days.
5240 * Months that are not |longMonths| have 30 days except February (month 2).
5241 * February has 29 days during leap years which are years that are divisible
5242 * by 400. or divisible by 100 and 4. February has 28 days otherwise.
5245 static const bool longMonths[] = {true, false, true, false, true, false,
5246 true, true, false, true, false, true};
5247 MOZ_ASSERT(aMonth <= 12 && aMonth > 0);
5249 if (longMonths[aMonth - 1]) {
5250 return 31;
5253 if (aMonth != 2) {
5254 return 30;
5257 return IsLeapYear(aYear) ? 29 : 28;
5260 /* static */
5261 bool HTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
5262 uint32_t aStart, uint32_t aLen,
5263 uint32_t* aRetVal) {
5264 MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
5266 for (uint32_t offset = 0; offset < aLen; ++offset) {
5267 if (!IsAsciiDigit(aStr[aStart + offset])) {
5268 return false;
5272 nsresult ec;
5273 *aRetVal = static_cast<uint32_t>(
5274 PromiseFlatString(Substring(aStr, aStart, aLen)).ToInteger(&ec));
5276 return NS_SUCCEEDED(ec);
5279 bool HTMLInputElement::IsValidTime(const nsAString& aValue) const {
5280 return ParseTime(aValue, nullptr);
5283 /* static */
5284 bool HTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult) {
5285 /* The string must have the following parts:
5286 * - HOURS: two digits, value being in [0, 23];
5287 * - Colon (:);
5288 * - MINUTES: two digits, value being in [0, 59];
5289 * - Optional:
5290 * - Colon (:);
5291 * - SECONDS: two digits, value being in [0, 59];
5292 * - Optional:
5293 * - DOT (.);
5294 * - FRACTIONAL SECONDS: one to three digits, no value range.
5297 // The following format is the shorter one allowed: "HH:MM".
5298 if (aValue.Length() < 5) {
5299 return false;
5302 uint32_t hours;
5303 if (!DigitSubStringToNumber(aValue, 0, 2, &hours) || hours > 23) {
5304 return false;
5307 // Hours/minutes separator.
5308 if (aValue[2] != ':') {
5309 return false;
5312 uint32_t minutes;
5313 if (!DigitSubStringToNumber(aValue, 3, 2, &minutes) || minutes > 59) {
5314 return false;
5317 if (aValue.Length() == 5) {
5318 if (aResult) {
5319 *aResult = ((hours * 60) + minutes) * 60000;
5321 return true;
5324 // The following format is the next shorter one: "HH:MM:SS".
5325 if (aValue.Length() < 8 || aValue[5] != ':') {
5326 return false;
5329 uint32_t seconds;
5330 if (!DigitSubStringToNumber(aValue, 6, 2, &seconds) || seconds > 59) {
5331 return false;
5334 if (aValue.Length() == 8) {
5335 if (aResult) {
5336 *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000;
5338 return true;
5341 // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
5342 // There can be 1 to 3 digits for the fractions of seconds.
5343 if (aValue.Length() == 9 || aValue.Length() > 12 || aValue[8] != '.') {
5344 return false;
5347 uint32_t fractionsSeconds;
5348 if (!DigitSubStringToNumber(aValue, 9, aValue.Length() - 9,
5349 &fractionsSeconds)) {
5350 return false;
5353 if (aResult) {
5354 *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000 +
5355 // NOTE: there is 10.0 instead of 10 and static_cast<int> because
5356 // some old [and stupid] compilers can't just do the right thing.
5357 fractionsSeconds *
5358 pow(10.0, static_cast<int>(3 - (aValue.Length() - 9)));
5361 return true;
5364 /* static */
5365 bool HTMLInputElement::IsDateTimeTypeSupported(
5366 FormControlType aDateTimeInputType) {
5367 switch (aDateTimeInputType) {
5368 case FormControlType::InputDate:
5369 case FormControlType::InputTime:
5370 case FormControlType::InputDatetimeLocal:
5371 return true;
5372 case FormControlType::InputMonth:
5373 case FormControlType::InputWeek:
5374 return StaticPrefs::dom_forms_datetime_others();
5375 default:
5376 return false;
5380 void HTMLInputElement::GetLastInteractiveValue(nsAString& aValue) {
5381 if (mLastValueChangeWasInteractive) {
5382 return GetValue(aValue, CallerType::System);
5384 if (TextControlState* state = GetEditorState()) {
5385 return aValue.Assign(
5386 state->LastInteractiveValueIfLastChangeWasNonInteractive());
5388 aValue.Truncate();
5391 bool HTMLInputElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
5392 const nsAString& aValue,
5393 nsIPrincipal* aMaybeScriptedPrincipal,
5394 nsAttrValue& aResult) {
5395 // We can't make these static_asserts because kInputDefaultType and
5396 // kInputTypeTable aren't constexpr.
5397 MOZ_ASSERT(
5398 FormControlType(kInputDefaultType->value) == FormControlType::InputText,
5399 "Someone forgot to update kInputDefaultType when adding a new "
5400 "input type.");
5401 MOZ_ASSERT(kInputTypeTable[ArrayLength(kInputTypeTable) - 1].tag == nullptr,
5402 "Last entry in the table must be the nullptr guard");
5403 MOZ_ASSERT(FormControlType(
5404 kInputTypeTable[ArrayLength(kInputTypeTable) - 2].value) ==
5405 FormControlType::InputText,
5406 "Next to last entry in the table must be the \"text\" entry");
5408 if (aNamespaceID == kNameSpaceID_None) {
5409 if (aAttribute == nsGkAtoms::type) {
5410 aResult.ParseEnumValue(aValue, kInputTypeTable, false, kInputDefaultType);
5411 auto newType = FormControlType(aResult.GetEnumValue());
5412 if (IsDateTimeInputType(newType) && !IsDateTimeTypeSupported(newType)) {
5413 // There's no public way to set an nsAttrValue to an enum value, but we
5414 // can just re-parse with a table that doesn't have any types other than
5415 // "text" in it.
5416 aResult.ParseEnumValue(aValue, kInputDefaultType, false,
5417 kInputDefaultType);
5420 return true;
5422 if (aAttribute == nsGkAtoms::width) {
5423 return aResult.ParseHTMLDimension(aValue);
5425 if (aAttribute == nsGkAtoms::height) {
5426 return aResult.ParseHTMLDimension(aValue);
5428 if (aAttribute == nsGkAtoms::maxlength) {
5429 return aResult.ParseNonNegativeIntValue(aValue);
5431 if (aAttribute == nsGkAtoms::minlength) {
5432 return aResult.ParseNonNegativeIntValue(aValue);
5434 if (aAttribute == nsGkAtoms::size) {
5435 return aResult.ParsePositiveIntValue(aValue);
5437 if (aAttribute == nsGkAtoms::align) {
5438 return ParseAlignValue(aValue, aResult);
5440 if (aAttribute == nsGkAtoms::formmethod) {
5441 return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
5443 if (aAttribute == nsGkAtoms::formenctype) {
5444 return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
5446 if (aAttribute == nsGkAtoms::autocomplete) {
5447 aResult.ParseAtomArray(aValue);
5448 return true;
5450 if (aAttribute == nsGkAtoms::capture) {
5451 return aResult.ParseEnumValue(aValue, kCaptureTable, false,
5452 kCaptureDefault);
5454 if (ParseImageAttribute(aAttribute, aValue, aResult)) {
5455 // We have to call |ParseImageAttribute| unconditionally since we
5456 // don't know if we're going to have a type="image" attribute yet,
5457 // (or could have it set dynamically in the future). See bug
5458 // 214077.
5459 return true;
5463 return TextControlElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
5464 aMaybeScriptedPrincipal, aResult);
5467 void HTMLInputElement::ImageInputMapAttributesIntoRule(
5468 MappedDeclarationsBuilder& aBuilder) {
5469 nsGenericHTMLFormControlElementWithState::MapImageBorderAttributeInto(
5470 aBuilder);
5471 nsGenericHTMLFormControlElementWithState::MapImageMarginAttributeInto(
5472 aBuilder);
5473 nsGenericHTMLFormControlElementWithState::MapImageSizeAttributesInto(
5474 aBuilder, MapAspectRatio::Yes);
5475 // Images treat align as "float"
5476 nsGenericHTMLFormControlElementWithState::MapImageAlignAttributeInto(
5477 aBuilder);
5478 nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder);
5481 nsChangeHint HTMLInputElement::GetAttributeChangeHint(const nsAtom* aAttribute,
5482 int32_t aModType) const {
5483 nsChangeHint retval =
5484 nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint(
5485 aAttribute, aModType);
5487 const bool isAdditionOrRemoval =
5488 aModType == MutationEvent_Binding::ADDITION ||
5489 aModType == MutationEvent_Binding::REMOVAL;
5491 const bool reconstruct = [&] {
5492 if (aAttribute == nsGkAtoms::type) {
5493 return true;
5496 if (PlaceholderApplies() && aAttribute == nsGkAtoms::placeholder &&
5497 isAdditionOrRemoval) {
5498 // We need to re-create our placeholder text.
5499 return true;
5502 if (mType == FormControlType::InputFile &&
5503 aAttribute == nsGkAtoms::webkitdirectory) {
5504 // The presence or absence of the 'directory' attribute determines what
5505 // value we show in the file label when empty, via GetDisplayFileName.
5506 return true;
5509 if (mType == FormControlType::InputImage && isAdditionOrRemoval &&
5510 (aAttribute == nsGkAtoms::alt || aAttribute == nsGkAtoms::value)) {
5511 // We might need to rebuild our alt text. Just go ahead and
5512 // reconstruct our frame. This should be quite rare..
5513 return true;
5515 return false;
5516 }();
5518 if (reconstruct) {
5519 retval |= nsChangeHint_ReconstructFrame;
5520 } else if (aAttribute == nsGkAtoms::value) {
5521 retval |= NS_STYLE_HINT_REFLOW;
5522 } else if (aAttribute == nsGkAtoms::size && IsSingleLineTextControl(false)) {
5523 retval |= NS_STYLE_HINT_REFLOW;
5526 return retval;
5529 NS_IMETHODIMP_(bool)
5530 HTMLInputElement::IsAttributeMapped(const nsAtom* aAttribute) const {
5531 static const MappedAttributeEntry attributes[] = {
5532 {nsGkAtoms::align},
5533 {nullptr},
5536 static const MappedAttributeEntry* const map[] = {
5537 attributes,
5538 sCommonAttributeMap,
5539 sImageMarginSizeAttributeMap,
5540 sImageBorderAttributeMap,
5543 return FindAttributeDependence(aAttribute, map);
5546 nsMapRuleToAttributesFunc HTMLInputElement::GetAttributeMappingFunction()
5547 const {
5548 // GetAttributeChangeHint guarantees that changes to mType will trigger a
5549 // reframe, and we update the mapping function in our mapped attrs when our
5550 // type changes, so it's safe to condition our attribute mapping function on
5551 // mType.
5552 if (mType == FormControlType::InputImage) {
5553 return &ImageInputMapAttributesIntoRule;
5556 return &MapCommonAttributesInto;
5559 // Directory picking methods:
5561 already_AddRefed<Promise> HTMLInputElement::GetFilesAndDirectories(
5562 ErrorResult& aRv) {
5563 if (mType != FormControlType::InputFile) {
5564 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5565 return nullptr;
5568 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
5569 MOZ_ASSERT(global);
5570 if (!global) {
5571 return nullptr;
5574 RefPtr<Promise> p = Promise::Create(global, aRv);
5575 if (aRv.Failed()) {
5576 return nullptr;
5579 const nsTArray<OwningFileOrDirectory>& filesAndDirs =
5580 GetFilesOrDirectoriesInternal();
5582 Sequence<OwningFileOrDirectory> filesAndDirsSeq;
5584 if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(), fallible)) {
5585 p->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
5586 return p.forget();
5589 for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) {
5590 if (filesAndDirs[i].IsDirectory()) {
5591 RefPtr<Directory> directory = filesAndDirs[i].GetAsDirectory();
5593 // In future we could refactor SetFilePickerFiltersFromAccept to return a
5594 // semicolon separated list of file extensions and include that in the
5595 // filter string passed here.
5596 directory->SetContentFilters(u"filter-out-sensitive"_ns);
5597 filesAndDirsSeq[i].SetAsDirectory() = directory;
5598 } else {
5599 MOZ_ASSERT(filesAndDirs[i].IsFile());
5601 // This file was directly selected by the user, so don't filter it.
5602 filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i].GetAsFile();
5606 p->MaybeResolve(filesAndDirsSeq);
5607 return p.forget();
5610 // Controllers Methods
5612 nsIControllers* HTMLInputElement::GetControllers(ErrorResult& aRv) {
5613 // XXX: what about type "file"?
5614 if (IsSingleLineTextControl(false)) {
5615 if (!mControllers) {
5616 mControllers = new nsXULControllers();
5617 if (!mControllers) {
5618 aRv.Throw(NS_ERROR_FAILURE);
5619 return nullptr;
5622 RefPtr<nsBaseCommandController> commandController =
5623 nsBaseCommandController::CreateEditorController();
5624 if (!commandController) {
5625 aRv.Throw(NS_ERROR_FAILURE);
5626 return nullptr;
5629 mControllers->AppendController(commandController);
5631 commandController = nsBaseCommandController::CreateEditingController();
5632 if (!commandController) {
5633 aRv.Throw(NS_ERROR_FAILURE);
5634 return nullptr;
5637 mControllers->AppendController(commandController);
5641 return mControllers;
5644 nsresult HTMLInputElement::GetControllers(nsIControllers** aResult) {
5645 NS_ENSURE_ARG_POINTER(aResult);
5647 ErrorResult rv;
5648 RefPtr<nsIControllers> controller = GetControllers(rv);
5649 controller.forget(aResult);
5650 return rv.StealNSResult();
5653 int32_t HTMLInputElement::InputTextLength(CallerType aCallerType) {
5654 nsAutoString val;
5655 GetValue(val, aCallerType);
5656 return val.Length();
5659 void HTMLInputElement::SetSelectionRange(uint32_t aSelectionStart,
5660 uint32_t aSelectionEnd,
5661 const Optional<nsAString>& aDirection,
5662 ErrorResult& aRv) {
5663 if (!SupportsTextSelection()) {
5664 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5665 return;
5668 TextControlState* state = GetEditorState();
5669 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5670 state->SetSelectionRange(aSelectionStart, aSelectionEnd, aDirection, aRv);
5673 void HTMLInputElement::SetRangeText(const nsAString& aReplacement,
5674 ErrorResult& aRv) {
5675 if (!SupportsTextSelection()) {
5676 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5677 return;
5680 TextControlState* state = GetEditorState();
5681 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5682 state->SetRangeText(aReplacement, aRv);
5685 void HTMLInputElement::SetRangeText(const nsAString& aReplacement,
5686 uint32_t aStart, uint32_t aEnd,
5687 SelectionMode aSelectMode,
5688 ErrorResult& aRv) {
5689 if (!SupportsTextSelection()) {
5690 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5691 return;
5694 TextControlState* state = GetEditorState();
5695 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5696 state->SetRangeText(aReplacement, aStart, aEnd, aSelectMode, aRv);
5699 void HTMLInputElement::GetValueFromSetRangeText(nsAString& aValue) {
5700 GetNonFileValueInternal(aValue);
5703 nsresult HTMLInputElement::SetValueFromSetRangeText(const nsAString& aValue) {
5704 return SetValueInternal(aValue, {ValueSetterOption::ByContentAPI,
5705 ValueSetterOption::BySetRangeTextAPI,
5706 ValueSetterOption::SetValueChanged});
5709 Nullable<uint32_t> HTMLInputElement::GetSelectionStart(ErrorResult& aRv) {
5710 if (!SupportsTextSelection()) {
5711 return Nullable<uint32_t>();
5714 uint32_t selStart = GetSelectionStartIgnoringType(aRv);
5715 if (aRv.Failed()) {
5716 return Nullable<uint32_t>();
5719 return Nullable<uint32_t>(selStart);
5722 uint32_t HTMLInputElement::GetSelectionStartIgnoringType(ErrorResult& aRv) {
5723 uint32_t selEnd, selStart;
5724 GetSelectionRange(&selStart, &selEnd, aRv);
5725 return selStart;
5728 void HTMLInputElement::SetSelectionStart(
5729 const Nullable<uint32_t>& aSelectionStart, ErrorResult& aRv) {
5730 if (!SupportsTextSelection()) {
5731 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5732 return;
5735 TextControlState* state = GetEditorState();
5736 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5737 state->SetSelectionStart(aSelectionStart, aRv);
5740 Nullable<uint32_t> HTMLInputElement::GetSelectionEnd(ErrorResult& aRv) {
5741 if (!SupportsTextSelection()) {
5742 return Nullable<uint32_t>();
5745 uint32_t selEnd = GetSelectionEndIgnoringType(aRv);
5746 if (aRv.Failed()) {
5747 return Nullable<uint32_t>();
5750 return Nullable<uint32_t>(selEnd);
5753 uint32_t HTMLInputElement::GetSelectionEndIgnoringType(ErrorResult& aRv) {
5754 uint32_t selEnd, selStart;
5755 GetSelectionRange(&selStart, &selEnd, aRv);
5756 return selEnd;
5759 void HTMLInputElement::SetSelectionEnd(const Nullable<uint32_t>& aSelectionEnd,
5760 ErrorResult& aRv) {
5761 if (!SupportsTextSelection()) {
5762 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5763 return;
5766 TextControlState* state = GetEditorState();
5767 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5768 state->SetSelectionEnd(aSelectionEnd, aRv);
5771 void HTMLInputElement::GetSelectionRange(uint32_t* aSelectionStart,
5772 uint32_t* aSelectionEnd,
5773 ErrorResult& aRv) {
5774 TextControlState* state = GetEditorState();
5775 if (!state) {
5776 // Not a text control.
5777 aRv.Throw(NS_ERROR_UNEXPECTED);
5778 return;
5781 state->GetSelectionRange(aSelectionStart, aSelectionEnd, aRv);
5784 void HTMLInputElement::GetSelectionDirection(nsAString& aDirection,
5785 ErrorResult& aRv) {
5786 if (!SupportsTextSelection()) {
5787 aDirection.SetIsVoid(true);
5788 return;
5791 TextControlState* state = GetEditorState();
5792 MOZ_ASSERT(state, "SupportsTextSelection came back true!");
5793 state->GetSelectionDirectionString(aDirection, aRv);
5796 void HTMLInputElement::SetSelectionDirection(const nsAString& aDirection,
5797 ErrorResult& aRv) {
5798 if (!SupportsTextSelection()) {
5799 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5800 return;
5803 TextControlState* state = GetEditorState();
5804 MOZ_ASSERT(state, "SupportsTextSelection came back true!");
5805 state->SetSelectionDirection(aDirection, aRv);
5808 // https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
5809 void HTMLInputElement::ShowPicker(ErrorResult& aRv) {
5810 // Step 1. If this is not mutable, then throw an "InvalidStateError"
5811 // DOMException.
5812 if (!IsMutable()) {
5813 return aRv.ThrowInvalidStateError(
5814 "This input is either disabled or readonly.");
5817 // Step 2. If this's relevant settings object's origin is not same origin with
5818 // this's relevant settings object's top-level origin, and this's type
5819 // attribute is not in the File Upload state or Color state, then throw a
5820 // "SecurityError" DOMException.
5821 if (mType != FormControlType::InputFile &&
5822 mType != FormControlType::InputColor) {
5823 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
5824 WindowGlobalChild* windowGlobalChild =
5825 window ? window->GetWindowGlobalChild() : nullptr;
5826 if (!windowGlobalChild || !windowGlobalChild->SameOriginWithTop()) {
5827 return aRv.ThrowSecurityError(
5828 "Call was blocked because the current origin isn't same-origin with "
5829 "top.");
5833 // Step 3. If this's relevant global object does not have transient
5834 // activation, then throw a "NotAllowedError" DOMException.
5835 if (!OwnerDoc()->HasValidTransientUserGestureActivation()) {
5836 return aRv.ThrowNotAllowedError(
5837 "Call was blocked due to lack of user activation.");
5840 // Step 4. Show the picker, if applicable, for this.
5842 // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
5843 // To show the picker, if applicable for an input element element:
5845 // Step 1. Assert: element's relevant global object has transient activation.
5846 // Step 2. If element is not mutable, then return.
5847 // (See above.)
5849 // Step 3. If element's type attribute is in the File Upload state, then run
5850 // these steps in parallel:
5851 if (mType == FormControlType::InputFile) {
5852 FilePickerType type = FILE_PICKER_FILE;
5853 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
5854 HasAttr(nsGkAtoms::webkitdirectory)) {
5855 type = FILE_PICKER_DIRECTORY;
5857 InitFilePicker(type);
5858 return;
5861 // Step 4. Otherwise, the user agent should show any relevant user interface
5862 // for selecting a value for element, in the way it normally would when the
5863 // user interacts with the control
5864 if (mType == FormControlType::InputColor) {
5865 InitColorPicker();
5866 return;
5869 if (!IsInComposedDoc()) {
5870 return;
5873 if (IsDateTimeTypeSupported(mType)) {
5874 if (CreatesDateTimeWidget()) {
5875 if (RefPtr<Element> dateTimeBoxElement = GetDateTimeBoxElement()) {
5876 // Event is dispatched to closed-shadow tree and doesn't bubble.
5877 RefPtr<Document> doc = dateTimeBoxElement->OwnerDoc();
5878 nsContentUtils::DispatchTrustedEvent(doc, dateTimeBoxElement,
5879 u"MozDateTimeShowPickerForJS"_ns,
5880 CanBubble::eNo, Cancelable::eNo);
5882 } else {
5883 DateTimeValue value;
5884 GetDateTimeInputBoxValue(value);
5885 OpenDateTimePicker(value);
5890 #ifdef ACCESSIBILITY
5891 /*static*/ nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
5892 EventMessage aEventMessage) {
5893 Element* element = static_cast<Element*>(aTarget);
5894 return nsContentUtils::DispatchTrustedEvent<WidgetEvent>(
5895 element->OwnerDoc(), element, aEventMessage, CanBubble::eYes,
5896 Cancelable::eYes);
5898 #endif
5900 void HTMLInputElement::UpdateApzAwareFlag() {
5901 #if !defined(ANDROID) && !defined(XP_MACOSX)
5902 if (mType == FormControlType::InputNumber ||
5903 mType == FormControlType::InputRange) {
5904 SetMayBeApzAware();
5906 #endif
5909 nsresult HTMLInputElement::SetDefaultValueAsValue() {
5910 NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE,
5911 "GetValueMode() should return VALUE_MODE_VALUE!");
5913 // The element has a content attribute value different from it's value when
5914 // it's in the value mode value.
5915 nsAutoString resetVal;
5916 GetDefaultValue(resetVal);
5918 // SetValueInternal is going to sanitize the value.
5919 // TODO(mbrodesser): sanitizing will only happen if `mDoneCreating` is true.
5920 return SetValueInternal(resetVal, ValueSetterOption::ByInternalAPI);
5923 // https://html.spec.whatwg.org/#auto-directionality
5924 void HTMLInputElement::SetAutoDirectionality(bool aNotify,
5925 const nsAString* aKnownValue) {
5926 if (!IsAutoDirectionalityAssociated()) {
5927 return SetDirectionality(GetParentDirectionality(this), aNotify);
5929 nsAutoString value;
5930 if (!aKnownValue) {
5931 // It's unclear if per spec we should use the sanitized or unsanitized
5932 // value to set the directionality, but aKnownValue is unsanitized, so be
5933 // consistent. Using what the user is seeing to determine directionality
5934 // instead of the sanitized (empty if invalid) value probably makes more
5935 // sense.
5936 GetValueInternal(value, CallerType::System);
5937 aKnownValue = &value;
5939 SetDirectionalityFromValue(this, *aKnownValue, aNotify);
5942 NS_IMETHODIMP
5943 HTMLInputElement::Reset() {
5944 // We should be able to reset all dirty flags regardless of the type.
5945 SetCheckedChanged(false);
5946 SetValueChanged(false);
5947 SetLastValueChangeWasInteractive(false);
5948 SetUserInteracted(false);
5950 switch (GetValueMode()) {
5951 case VALUE_MODE_VALUE: {
5952 nsresult result = SetDefaultValueAsValue();
5953 if (CreatesDateTimeWidget()) {
5954 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded`
5955 // can fire a change event if necessary.
5956 GetValue(mFocusedValue, CallerType::System);
5958 return result;
5960 case VALUE_MODE_DEFAULT_ON:
5961 DoSetChecked(DefaultChecked(), true, false);
5962 return NS_OK;
5963 case VALUE_MODE_FILENAME:
5964 ClearFiles(false);
5965 return NS_OK;
5966 case VALUE_MODE_DEFAULT:
5967 default:
5968 return NS_OK;
5972 NS_IMETHODIMP
5973 HTMLInputElement::SubmitNamesValues(FormData* aFormData) {
5974 // For type=reset, and type=button, we just never submit, period.
5975 // For type=image and type=button, we only submit if we were the button
5976 // pressed
5977 // For type=radio and type=checkbox, we only submit if checked=true
5978 if (mType == FormControlType::InputReset ||
5979 mType == FormControlType::InputButton ||
5980 ((mType == FormControlType::InputSubmit ||
5981 mType == FormControlType::InputImage) &&
5982 aFormData->GetSubmitterElement() != this) ||
5983 ((mType == FormControlType::InputRadio ||
5984 mType == FormControlType::InputCheckbox) &&
5985 !mChecked)) {
5986 return NS_OK;
5989 // Get the name
5990 nsAutoString name;
5991 GetAttr(nsGkAtoms::name, name);
5993 // Submit .x, .y for input type=image
5994 if (mType == FormControlType::InputImage) {
5995 // Get a property set by the frame to find out where it was clicked.
5996 const auto* lastClickedPoint =
5997 static_cast<CSSIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
5998 int32_t x, y;
5999 if (lastClickedPoint) {
6000 // Convert the values to strings for submission
6001 x = lastClickedPoint->x;
6002 y = lastClickedPoint->y;
6003 } else {
6004 x = y = 0;
6007 nsAutoString xVal, yVal;
6008 xVal.AppendInt(x);
6009 yVal.AppendInt(y);
6011 if (!name.IsEmpty()) {
6012 aFormData->AddNameValuePair(name + u".x"_ns, xVal);
6013 aFormData->AddNameValuePair(name + u".y"_ns, yVal);
6014 } else {
6015 // If the Image Element has no name, simply return x and y
6016 // to Nav and IE compatibility.
6017 aFormData->AddNameValuePair(u"x"_ns, xVal);
6018 aFormData->AddNameValuePair(u"y"_ns, yVal);
6021 return NS_OK;
6024 // If name not there, don't submit
6025 if (name.IsEmpty()) {
6026 return NS_OK;
6030 // Submit file if its input type=file and this encoding method accepts files
6032 if (mType == FormControlType::InputFile) {
6033 // Submit files
6035 const nsTArray<OwningFileOrDirectory>& files =
6036 GetFilesOrDirectoriesInternal();
6038 if (files.IsEmpty()) {
6039 NS_ENSURE_STATE(GetOwnerGlobal());
6040 ErrorResult rv;
6041 RefPtr<Blob> blob = Blob::CreateStringBlob(
6042 GetOwnerGlobal(), ""_ns, u"application/octet-stream"_ns);
6043 RefPtr<File> file = blob->ToFile(u""_ns, rv);
6045 if (!rv.Failed()) {
6046 aFormData->AddNameBlobPair(name, file);
6049 return rv.StealNSResult();
6052 for (uint32_t i = 0; i < files.Length(); ++i) {
6053 if (files[i].IsFile()) {
6054 aFormData->AddNameBlobPair(name, files[i].GetAsFile());
6055 } else {
6056 MOZ_ASSERT(files[i].IsDirectory());
6057 aFormData->AddNameDirectoryPair(name, files[i].GetAsDirectory());
6061 return NS_OK;
6064 if (mType == FormControlType::InputHidden &&
6065 name.LowerCaseEqualsLiteral("_charset_")) {
6066 nsCString charset;
6067 aFormData->GetCharset(charset);
6068 return aFormData->AddNameValuePair(name, NS_ConvertASCIItoUTF16(charset));
6072 // Submit name=value
6075 // Get the value
6076 nsAutoString value;
6077 GetValue(value, CallerType::System);
6079 if (mType == FormControlType::InputSubmit && value.IsEmpty() &&
6080 !HasAttr(nsGkAtoms::value)) {
6081 // Get our default value, which is the same as our default label
6082 nsAutoString defaultValue;
6083 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
6084 "Submit", OwnerDoc(), defaultValue);
6085 value = defaultValue;
6088 const nsresult rv = aFormData->AddNameValuePair(name, value);
6089 if (NS_FAILED(rv)) {
6090 return rv;
6093 // Submit dirname=dir
6094 if (IsAutoDirectionalityAssociated()) {
6095 return SubmitDirnameDir(aFormData);
6098 return NS_OK;
6101 static nsTArray<FileContentData> SaveFileContentData(
6102 const nsTArray<OwningFileOrDirectory>& aArray) {
6103 nsTArray<FileContentData> res(aArray.Length());
6104 for (const auto& it : aArray) {
6105 if (it.IsFile()) {
6106 RefPtr<BlobImpl> impl = it.GetAsFile()->Impl();
6107 res.AppendElement(std::move(impl));
6108 } else {
6109 MOZ_ASSERT(it.IsDirectory());
6110 nsString fullPath;
6111 nsresult rv = it.GetAsDirectory()->GetFullRealPath(fullPath);
6112 if (NS_WARN_IF(NS_FAILED(rv))) {
6113 continue;
6115 res.AppendElement(std::move(fullPath));
6118 return res;
6121 void HTMLInputElement::SaveState() {
6122 PresState* state = nullptr;
6123 switch (GetValueMode()) {
6124 case VALUE_MODE_DEFAULT_ON:
6125 if (mCheckedChanged) {
6126 state = GetPrimaryPresState();
6127 if (!state) {
6128 return;
6131 state->contentData() = CheckedContentData(mChecked);
6133 break;
6134 case VALUE_MODE_FILENAME:
6135 if (!mFileData->mFilesOrDirectories.IsEmpty()) {
6136 state = GetPrimaryPresState();
6137 if (!state) {
6138 return;
6141 state->contentData() =
6142 SaveFileContentData(mFileData->mFilesOrDirectories);
6144 break;
6145 case VALUE_MODE_VALUE:
6146 case VALUE_MODE_DEFAULT:
6147 // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
6148 // mType should have never been FormControlType::InputPassword and value
6149 // should have changed.
6150 if ((GetValueMode() == VALUE_MODE_DEFAULT &&
6151 mType != FormControlType::InputHidden) ||
6152 mHasBeenTypePassword || !mValueChanged) {
6153 break;
6156 state = GetPrimaryPresState();
6157 if (!state) {
6158 return;
6161 nsAutoString value;
6162 GetValue(value, CallerType::System);
6164 if (!IsSingleLineTextControl(false) &&
6165 NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks(
6166 value, nsLinebreakConverter::eLinebreakPlatform,
6167 nsLinebreakConverter::eLinebreakContent))) {
6168 NS_ERROR("Converting linebreaks failed!");
6169 return;
6172 state->contentData() =
6173 TextContentData(value, mLastValueChangeWasInteractive);
6174 break;
6177 if (mDisabledChanged) {
6178 if (!state) {
6179 state = GetPrimaryPresState();
6181 if (state) {
6182 // We do not want to save the real disabled state but the disabled
6183 // attribute.
6184 state->disabled() = HasAttr(nsGkAtoms::disabled);
6185 state->disabledSet() = true;
6190 void HTMLInputElement::DoneCreatingElement() {
6191 mDoneCreating = true;
6194 // Restore state as needed. Note that disabled state applies to all control
6195 // types.
6197 bool restoredCheckedState = false;
6198 if (!mInhibitRestoration) {
6199 GenerateStateKey();
6200 restoredCheckedState = RestoreFormControlState();
6204 // If restore does not occur, we initialize .checked using the CHECKED
6205 // property.
6207 if (!restoredCheckedState && mShouldInitChecked) {
6208 DoSetChecked(DefaultChecked(), false, false);
6211 // Sanitize the value and potentially set mFocusedValue.
6212 if (GetValueMode() == VALUE_MODE_VALUE) {
6213 nsAutoString value;
6214 GetValue(value, CallerType::System);
6215 // TODO: What should we do if SetValueInternal fails? (The allocation
6216 // may potentially be big, but most likely we've failed to allocate
6217 // before the type change.)
6218 SetValueInternal(value, ValueSetterOption::ByInternalAPI);
6220 if (CreatesDateTimeWidget()) {
6221 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded` can
6222 // fire a change event if necessary.
6223 mFocusedValue = value;
6227 mShouldInitChecked = false;
6230 void HTMLInputElement::DestroyContent() {
6231 nsImageLoadingContent::Destroy();
6232 TextControlElement::DestroyContent();
6235 void HTMLInputElement::UpdateValidityElementStates(bool aNotify) {
6236 AutoStateChangeNotifier notifier(*this, aNotify);
6237 RemoveStatesSilently(ElementState::VALIDITY_STATES);
6238 if (!IsCandidateForConstraintValidation()) {
6239 return;
6241 ElementState state;
6242 if (IsValid()) {
6243 state |= ElementState::VALID;
6244 if (mUserInteracted) {
6245 state |= ElementState::USER_VALID;
6247 } else {
6248 state |= ElementState::INVALID;
6249 if (mUserInteracted) {
6250 state |= ElementState::USER_INVALID;
6253 AddStatesSilently(state);
6256 static nsTArray<OwningFileOrDirectory> RestoreFileContentData(
6257 nsPIDOMWindowInner* aWindow, const nsTArray<FileContentData>& aData) {
6258 nsTArray<OwningFileOrDirectory> res(aData.Length());
6259 for (const auto& it : aData) {
6260 if (it.type() == FileContentData::TBlobImpl) {
6261 if (!it.get_BlobImpl()) {
6262 // Serialization failed, skip this file.
6263 continue;
6266 RefPtr<File> file = File::Create(aWindow->AsGlobal(), it.get_BlobImpl());
6267 if (NS_WARN_IF(!file)) {
6268 continue;
6271 OwningFileOrDirectory* element = res.AppendElement();
6272 element->SetAsFile() = file;
6273 } else {
6274 MOZ_ASSERT(it.type() == FileContentData::TnsString);
6275 nsCOMPtr<nsIFile> file;
6276 nsresult rv =
6277 NS_NewLocalFile(it.get_nsString(), true, getter_AddRefs(file));
6278 if (NS_WARN_IF(NS_FAILED(rv))) {
6279 continue;
6282 RefPtr<Directory> directory =
6283 Directory::Create(aWindow->AsGlobal(), file);
6284 MOZ_ASSERT(directory);
6286 OwningFileOrDirectory* element = res.AppendElement();
6287 element->SetAsDirectory() = directory;
6290 return res;
6293 bool HTMLInputElement::RestoreState(PresState* aState) {
6294 bool restoredCheckedState = false;
6296 const PresContentData& inputState = aState->contentData();
6298 switch (GetValueMode()) {
6299 case VALUE_MODE_DEFAULT_ON:
6300 if (inputState.type() == PresContentData::TCheckedContentData) {
6301 restoredCheckedState = true;
6302 bool checked = inputState.get_CheckedContentData().checked();
6303 DoSetChecked(checked, true, true);
6305 break;
6306 case VALUE_MODE_FILENAME:
6307 if (inputState.type() == PresContentData::TArrayOfFileContentData) {
6308 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
6309 if (window) {
6310 nsTArray<OwningFileOrDirectory> array =
6311 RestoreFileContentData(window, inputState);
6312 SetFilesOrDirectories(array, true);
6315 break;
6316 case VALUE_MODE_VALUE:
6317 case VALUE_MODE_DEFAULT:
6318 if (GetValueMode() == VALUE_MODE_DEFAULT &&
6319 mType != FormControlType::InputHidden) {
6320 break;
6323 if (inputState.type() == PresContentData::TTextContentData) {
6324 // TODO: What should we do if SetValueInternal fails? (The allocation
6325 // may potentially be big, but most likely we've failed to allocate
6326 // before the type change.)
6327 SetValueInternal(inputState.get_TextContentData().value(),
6328 ValueSetterOption::SetValueChanged);
6329 if (inputState.get_TextContentData().lastValueChangeWasInteractive()) {
6330 SetLastValueChangeWasInteractive(true);
6333 break;
6336 if (aState->disabledSet() && !aState->disabled()) {
6337 SetDisabled(false, IgnoreErrors());
6340 return restoredCheckedState;
6344 * Radio group stuff
6347 void HTMLInputElement::AddToRadioGroup() {
6348 MOZ_ASSERT(!mRadioGroupContainer,
6349 "Radio button must be removed from previous radio group container "
6350 "before being added to another!");
6352 // If the element has no radio group container we can stop here.
6353 auto* container = FindTreeRadioGroupContainer();
6354 if (!container) {
6355 return;
6358 nsAutoString name;
6359 GetAttr(nsGkAtoms::name, name);
6360 // If we are part of a radio group, the element must have a name.
6361 MOZ_ASSERT(!name.IsEmpty());
6364 // Add the radio to the radio group container.
6366 container->AddToRadioGroup(name, this, mForm);
6367 mRadioGroupContainer = container;
6370 // If the input element is checked, and we add it to the group, it will
6371 // deselect whatever is currently selected in that group
6373 if (mChecked) {
6375 // If it is checked, call "RadioSetChecked" to perform the selection/
6376 // deselection ritual. This has the side effect of repainting the
6377 // radio button, but as adding a checked radio button into the group
6378 // should not be that common an occurrence, I think we can live with
6379 // that.
6380 // Make sure not to notify if we're still being created.
6382 RadioSetChecked(mDoneCreating);
6383 } else {
6384 bool indeterminate = !container->GetCurrentRadioButton(name);
6385 SetStates(ElementState::INDETERMINATE, indeterminate, mDoneCreating);
6389 // For integrity purposes, we have to ensure that "checkedChanged" is
6390 // the same for this new element as for all the others in the group
6392 bool checkedChanged = mCheckedChanged;
6394 nsCOMPtr<nsIRadioVisitor> visitor =
6395 new nsRadioGetCheckedChangedVisitor(&checkedChanged, this);
6396 VisitGroup(visitor);
6398 SetCheckedChangedInternal(checkedChanged);
6400 // We initialize the validity of the element to the validity of the group
6401 // because we assume UpdateValueMissingState() will be called after.
6402 SetValidityState(VALIDITY_STATE_VALUE_MISSING,
6403 container->GetValueMissingState(name));
6406 void HTMLInputElement::RemoveFromRadioGroup() {
6407 auto* container = GetCurrentRadioGroupContainer();
6408 if (!container) {
6409 return;
6412 nsAutoString name;
6413 GetAttr(nsGkAtoms::name, name);
6415 // If this button was checked, we need to notify the group that there is no
6416 // longer a selected radio button
6417 if (mChecked) {
6418 container->SetCurrentRadioButton(name, nullptr);
6419 nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
6420 VisitGroup(visitor);
6421 } else {
6422 AddStates(ElementState::INDETERMINATE);
6425 // Remove this radio from its group in the container.
6426 // We need to call UpdateValueMissingValidityStateForRadio before to make sure
6427 // the group validity is updated (with this element being ignored).
6428 UpdateValueMissingValidityStateForRadio(true);
6429 container->RemoveFromRadioGroup(name, this);
6430 mRadioGroupContainer = nullptr;
6433 bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
6434 int32_t* aTabIndex) {
6435 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
6436 aWithMouse, aIsFocusable, aTabIndex)) {
6437 return true;
6440 if (IsDisabled()) {
6441 *aIsFocusable = false;
6442 return true;
6445 if (IsSingleLineTextControl(false) || mType == FormControlType::InputRange) {
6446 *aIsFocusable = true;
6447 return false;
6450 const bool defaultFocusable = IsFormControlDefaultFocusable(aWithMouse);
6451 if (CreatesDateTimeWidget()) {
6452 if (aTabIndex) {
6453 // We only want our native anonymous child to be tabable to, not ourself.
6454 *aTabIndex = -1;
6456 *aIsFocusable = true;
6457 return true;
6460 if (mType == FormControlType::InputHidden) {
6461 if (aTabIndex) {
6462 *aTabIndex = -1;
6464 *aIsFocusable = false;
6465 return false;
6468 if (!aTabIndex) {
6469 // The other controls are all focusable
6470 *aIsFocusable = defaultFocusable;
6471 return false;
6474 if (mType != FormControlType::InputRadio) {
6475 *aIsFocusable = defaultFocusable;
6476 return false;
6479 if (mChecked) {
6480 // Selected radio buttons are tabbable
6481 *aIsFocusable = defaultFocusable;
6482 return false;
6485 // Current radio button is not selected.
6486 // But make it tabbable if nothing in group is selected.
6487 auto* container = GetCurrentRadioGroupContainer();
6488 if (!container) {
6489 *aIsFocusable = defaultFocusable;
6490 return false;
6493 nsAutoString name;
6494 GetAttr(nsGkAtoms::name, name);
6496 if (container->GetCurrentRadioButton(name)) {
6497 *aTabIndex = -1;
6499 *aIsFocusable = defaultFocusable;
6500 return false;
6503 nsresult HTMLInputElement::VisitGroup(nsIRadioVisitor* aVisitor) {
6504 if (auto* container = GetCurrentRadioGroupContainer()) {
6505 nsAutoString name;
6506 GetAttr(nsGkAtoms::name, name);
6507 return container->WalkRadioGroup(name, aVisitor);
6510 aVisitor->Visit(this);
6511 return NS_OK;
6514 HTMLInputElement::ValueModeType HTMLInputElement::GetValueMode() const {
6515 switch (mType) {
6516 case FormControlType::InputHidden:
6517 case FormControlType::InputSubmit:
6518 case FormControlType::InputButton:
6519 case FormControlType::InputReset:
6520 case FormControlType::InputImage:
6521 return VALUE_MODE_DEFAULT;
6522 case FormControlType::InputCheckbox:
6523 case FormControlType::InputRadio:
6524 return VALUE_MODE_DEFAULT_ON;
6525 case FormControlType::InputFile:
6526 return VALUE_MODE_FILENAME;
6527 #ifdef DEBUG
6528 case FormControlType::InputText:
6529 case FormControlType::InputPassword:
6530 case FormControlType::InputSearch:
6531 case FormControlType::InputTel:
6532 case FormControlType::InputEmail:
6533 case FormControlType::InputUrl:
6534 case FormControlType::InputNumber:
6535 case FormControlType::InputRange:
6536 case FormControlType::InputDate:
6537 case FormControlType::InputTime:
6538 case FormControlType::InputColor:
6539 case FormControlType::InputMonth:
6540 case FormControlType::InputWeek:
6541 case FormControlType::InputDatetimeLocal:
6542 return VALUE_MODE_VALUE;
6543 default:
6544 MOZ_ASSERT_UNREACHABLE("Unexpected input type in GetValueMode()");
6545 return VALUE_MODE_VALUE;
6546 #else // DEBUG
6547 default:
6548 return VALUE_MODE_VALUE;
6549 #endif // DEBUG
6553 bool HTMLInputElement::IsMutable() const {
6554 return !IsDisabled() &&
6555 !(DoesReadOnlyApply() && State().HasState(ElementState::READONLY));
6558 bool HTMLInputElement::DoesRequiredApply() const {
6559 switch (mType) {
6560 case FormControlType::InputHidden:
6561 case FormControlType::InputButton:
6562 case FormControlType::InputImage:
6563 case FormControlType::InputReset:
6564 case FormControlType::InputSubmit:
6565 case FormControlType::InputRange:
6566 case FormControlType::InputColor:
6567 return false;
6568 #ifdef DEBUG
6569 case FormControlType::InputRadio:
6570 case FormControlType::InputCheckbox:
6571 case FormControlType::InputFile:
6572 case FormControlType::InputText:
6573 case FormControlType::InputPassword:
6574 case FormControlType::InputSearch:
6575 case FormControlType::InputTel:
6576 case FormControlType::InputEmail:
6577 case FormControlType::InputUrl:
6578 case FormControlType::InputNumber:
6579 case FormControlType::InputDate:
6580 case FormControlType::InputTime:
6581 case FormControlType::InputMonth:
6582 case FormControlType::InputWeek:
6583 case FormControlType::InputDatetimeLocal:
6584 return true;
6585 default:
6586 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6587 return true;
6588 #else // DEBUG
6589 default:
6590 return true;
6591 #endif // DEBUG
6595 bool HTMLInputElement::PlaceholderApplies() const {
6596 if (IsDateTimeInputType(mType)) {
6597 return false;
6599 return IsSingleLineTextControl(false);
6602 bool HTMLInputElement::DoesMinMaxApply() const {
6603 switch (mType) {
6604 case FormControlType::InputNumber:
6605 case FormControlType::InputDate:
6606 case FormControlType::InputTime:
6607 case FormControlType::InputRange:
6608 case FormControlType::InputMonth:
6609 case FormControlType::InputWeek:
6610 case FormControlType::InputDatetimeLocal:
6611 return true;
6612 #ifdef DEBUG
6613 case FormControlType::InputReset:
6614 case FormControlType::InputSubmit:
6615 case FormControlType::InputImage:
6616 case FormControlType::InputButton:
6617 case FormControlType::InputHidden:
6618 case FormControlType::InputRadio:
6619 case FormControlType::InputCheckbox:
6620 case FormControlType::InputFile:
6621 case FormControlType::InputText:
6622 case FormControlType::InputPassword:
6623 case FormControlType::InputSearch:
6624 case FormControlType::InputTel:
6625 case FormControlType::InputEmail:
6626 case FormControlType::InputUrl:
6627 case FormControlType::InputColor:
6628 return false;
6629 default:
6630 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6631 return false;
6632 #else // DEBUG
6633 default:
6634 return false;
6635 #endif // DEBUG
6639 bool HTMLInputElement::DoesAutocompleteApply() const {
6640 switch (mType) {
6641 case FormControlType::InputHidden:
6642 case FormControlType::InputText:
6643 case FormControlType::InputSearch:
6644 case FormControlType::InputUrl:
6645 case FormControlType::InputTel:
6646 case FormControlType::InputEmail:
6647 case FormControlType::InputPassword:
6648 case FormControlType::InputDate:
6649 case FormControlType::InputTime:
6650 case FormControlType::InputNumber:
6651 case FormControlType::InputRange:
6652 case FormControlType::InputColor:
6653 case FormControlType::InputMonth:
6654 case FormControlType::InputWeek:
6655 case FormControlType::InputDatetimeLocal:
6656 return true;
6657 #ifdef DEBUG
6658 case FormControlType::InputReset:
6659 case FormControlType::InputSubmit:
6660 case FormControlType::InputImage:
6661 case FormControlType::InputButton:
6662 case FormControlType::InputRadio:
6663 case FormControlType::InputCheckbox:
6664 case FormControlType::InputFile:
6665 return false;
6666 default:
6667 MOZ_ASSERT_UNREACHABLE(
6668 "Unexpected input type in DoesAutocompleteApply()");
6669 return false;
6670 #else // DEBUG
6671 default:
6672 return false;
6673 #endif // DEBUG
6677 Decimal HTMLInputElement::GetStep() const {
6678 MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
6680 if (!HasAttr(nsGkAtoms::step)) {
6681 return GetDefaultStep() * GetStepScaleFactor();
6684 nsAutoString stepStr;
6685 GetAttr(nsGkAtoms::step, stepStr);
6687 if (stepStr.LowerCaseEqualsLiteral("any")) {
6688 // The element can't suffer from step mismatch if there is no step.
6689 return kStepAny;
6692 Decimal step = StringToDecimal(stepStr);
6693 if (!step.isFinite() || step <= Decimal(0)) {
6694 step = GetDefaultStep();
6697 // For input type=date, we round the step value to have a rounded day.
6698 if (mType == FormControlType::InputDate ||
6699 mType == FormControlType::InputMonth ||
6700 mType == FormControlType::InputWeek) {
6701 step = std::max(step.round(), Decimal(1));
6704 return step * GetStepScaleFactor();
6707 // ConstraintValidation
6709 void HTMLInputElement::SetCustomValidity(const nsAString& aError) {
6710 ConstraintValidation::SetCustomValidity(aError);
6711 UpdateValidityElementStates(true);
6714 bool HTMLInputElement::IsTooLong() {
6715 if (!mValueChanged || !mLastValueChangeWasInteractive) {
6716 return false;
6719 return mInputType->IsTooLong();
6722 bool HTMLInputElement::IsTooShort() {
6723 if (!mValueChanged || !mLastValueChangeWasInteractive) {
6724 return false;
6727 return mInputType->IsTooShort();
6730 bool HTMLInputElement::IsValueMissing() const {
6731 // Should use UpdateValueMissingValidityStateForRadio() for type radio.
6732 MOZ_ASSERT(mType != FormControlType::InputRadio);
6734 return mInputType->IsValueMissing();
6737 bool HTMLInputElement::HasTypeMismatch() const {
6738 return mInputType->HasTypeMismatch();
6741 Maybe<bool> HTMLInputElement::HasPatternMismatch() const {
6742 return mInputType->HasPatternMismatch();
6745 bool HTMLInputElement::IsRangeOverflow() const {
6746 return mInputType->IsRangeOverflow();
6749 bool HTMLInputElement::IsRangeUnderflow() const {
6750 return mInputType->IsRangeUnderflow();
6753 bool HTMLInputElement::ValueIsStepMismatch(const Decimal& aValue) const {
6754 if (aValue.isNaN()) {
6755 // The element can't suffer from step mismatch if its value isn't a
6756 // number.
6757 return false;
6760 Decimal step = GetStep();
6761 if (step == kStepAny) {
6762 return false;
6765 // Value has to be an integral multiple of step.
6766 return NS_floorModulo(aValue - GetStepBase(), step) != Decimal(0);
6769 bool HTMLInputElement::HasStepMismatch() const {
6770 return mInputType->HasStepMismatch();
6773 bool HTMLInputElement::HasBadInput() const { return mInputType->HasBadInput(); }
6775 void HTMLInputElement::UpdateTooLongValidityState() {
6776 SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
6779 void HTMLInputElement::UpdateTooShortValidityState() {
6780 SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort());
6783 void HTMLInputElement::UpdateValueMissingValidityStateForRadio(
6784 bool aIgnoreSelf) {
6785 MOZ_ASSERT(mType == FormControlType::InputRadio,
6786 "This should be called only for radio input types");
6788 HTMLInputElement* selection = GetSelectedRadioButton();
6790 // If there is no selection, that might mean the radio is not in a group.
6791 // In that case, we can look for the checked state of the radio.
6792 bool selected = selection || (!aIgnoreSelf && mChecked);
6793 bool required = !aIgnoreSelf && IsRequired();
6795 auto* container = GetCurrentRadioGroupContainer();
6796 if (!container) {
6797 SetValidityState(VALIDITY_STATE_VALUE_MISSING, false);
6798 return;
6801 nsAutoString name;
6802 GetAttr(nsGkAtoms::name, name);
6804 // If the current radio is required and not ignored, we can assume the entire
6805 // group is required.
6806 if (!required) {
6807 required = (aIgnoreSelf && IsRequired())
6808 ? container->GetRequiredRadioCount(name) - 1
6809 : container->GetRequiredRadioCount(name);
6812 bool valueMissing = required && !selected;
6813 if (container->GetValueMissingState(name) != valueMissing) {
6814 container->SetValueMissingState(name, valueMissing);
6816 SetValidityState(VALIDITY_STATE_VALUE_MISSING, valueMissing);
6818 // nsRadioSetValueMissingState will call ElementStateChanged while visiting.
6819 nsAutoScriptBlocker scriptBlocker;
6820 nsCOMPtr<nsIRadioVisitor> visitor =
6821 new nsRadioSetValueMissingState(this, valueMissing);
6822 VisitGroup(visitor);
6826 void HTMLInputElement::UpdateValueMissingValidityState() {
6827 if (mType == FormControlType::InputRadio) {
6828 UpdateValueMissingValidityStateForRadio(false);
6829 return;
6832 SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
6835 void HTMLInputElement::UpdateTypeMismatchValidityState() {
6836 SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, HasTypeMismatch());
6839 void HTMLInputElement::UpdatePatternMismatchValidityState() {
6840 Maybe<bool> hasMismatch = HasPatternMismatch();
6841 // Don't update if the JS engine failed to evaluate it.
6842 if (hasMismatch.isSome()) {
6843 SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, hasMismatch.value());
6847 void HTMLInputElement::UpdateRangeOverflowValidityState() {
6848 SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, IsRangeOverflow());
6849 UpdateInRange(true);
6852 void HTMLInputElement::UpdateRangeUnderflowValidityState() {
6853 SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, IsRangeUnderflow());
6854 UpdateInRange(true);
6857 void HTMLInputElement::UpdateStepMismatchValidityState() {
6858 SetValidityState(VALIDITY_STATE_STEP_MISMATCH, HasStepMismatch());
6861 void HTMLInputElement::UpdateBadInputValidityState() {
6862 SetValidityState(VALIDITY_STATE_BAD_INPUT, HasBadInput());
6865 void HTMLInputElement::UpdateAllValidityStates(bool aNotify) {
6866 bool validBefore = IsValid();
6867 UpdateAllValidityStatesButNotElementState();
6868 if (validBefore != IsValid()) {
6869 UpdateValidityElementStates(aNotify);
6873 void HTMLInputElement::UpdateAllValidityStatesButNotElementState() {
6874 UpdateTooLongValidityState();
6875 UpdateTooShortValidityState();
6876 UpdateValueMissingValidityState();
6877 UpdateTypeMismatchValidityState();
6878 UpdatePatternMismatchValidityState();
6879 UpdateRangeOverflowValidityState();
6880 UpdateRangeUnderflowValidityState();
6881 UpdateStepMismatchValidityState();
6882 UpdateBadInputValidityState();
6885 void HTMLInputElement::UpdateBarredFromConstraintValidation() {
6886 // NOTE: readonly attribute causes an element to be barred from constraint
6887 // validation even if it doesn't apply to that input type. That's rather
6888 // weird, but pre-existing behavior.
6889 bool wasCandidate = IsCandidateForConstraintValidation();
6890 SetBarredFromConstraintValidation(
6891 mType == FormControlType::InputHidden ||
6892 mType == FormControlType::InputButton ||
6893 mType == FormControlType::InputReset || IsDisabled() ||
6894 HasAttr(nsGkAtoms::readonly) ||
6895 HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR));
6896 if (IsCandidateForConstraintValidation() != wasCandidate) {
6897 UpdateInRange(true);
6901 nsresult HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
6902 ValidityStateType aType) {
6903 return mInputType->GetValidationMessage(aValidationMessage, aType);
6906 bool HTMLInputElement::IsSingleLineTextControl() const {
6907 return IsSingleLineTextControl(false);
6910 bool HTMLInputElement::IsTextArea() const { return false; }
6912 bool HTMLInputElement::IsPasswordTextControl() const {
6913 return mType == FormControlType::InputPassword;
6916 int32_t HTMLInputElement::GetCols() {
6917 // Else we know (assume) it is an input with size attr
6918 const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::size);
6919 if (attr && attr->Type() == nsAttrValue::eInteger) {
6920 int32_t cols = attr->GetIntegerValue();
6921 if (cols > 0) {
6922 return cols;
6926 return DEFAULT_COLS;
6929 int32_t HTMLInputElement::GetWrapCols() {
6930 return 0; // only textarea's can have wrap cols
6933 int32_t HTMLInputElement::GetRows() { return DEFAULT_ROWS; }
6935 void HTMLInputElement::GetDefaultValueFromContent(nsAString& aValue,
6936 bool aForDisplay) {
6937 if (!GetEditorState()) {
6938 return;
6940 GetDefaultValue(aValue);
6941 // This is called by the frame to show the value.
6942 // We have to sanitize it when needed.
6943 // FIXME: Do we want to sanitize even when aForDisplay is false?
6944 if (mDoneCreating) {
6945 SanitizeValue(aValue, aForDisplay ? SanitizationKind::ForDisplay
6946 : SanitizationKind::ForValueGetter);
6950 bool HTMLInputElement::ValueChanged() const { return mValueChanged; }
6952 void HTMLInputElement::GetTextEditorValue(nsAString& aValue) const {
6953 if (TextControlState* state = GetEditorState()) {
6954 state->GetValue(aValue, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
6958 void HTMLInputElement::InitializeKeyboardEventListeners() {
6959 TextControlState* state = GetEditorState();
6960 if (state) {
6961 state->InitializeKeyboardEventListeners();
6965 void HTMLInputElement::UpdatePlaceholderShownState() {
6966 SetStates(ElementState::PLACEHOLDER_SHOWN,
6967 IsValueEmpty() && PlaceholderApplies() &&
6968 HasAttr(nsGkAtoms::placeholder));
6971 void HTMLInputElement::OnValueChanged(ValueChangeKind aKind,
6972 bool aNewValueEmpty,
6973 const nsAString* aKnownNewValue) {
6974 MOZ_ASSERT_IF(aKnownNewValue, aKnownNewValue->IsEmpty() == aNewValueEmpty);
6975 if (aKind != ValueChangeKind::Internal) {
6976 mLastValueChangeWasInteractive = aKind == ValueChangeKind::UserInteraction;
6979 if (aNewValueEmpty != IsValueEmpty()) {
6980 SetStates(ElementState::VALUE_EMPTY, aNewValueEmpty);
6981 UpdatePlaceholderShownState();
6984 UpdateAllValidityStates(true);
6986 if (HasDirAuto()) {
6987 SetAutoDirectionality(true, aKnownNewValue);
6991 bool HTMLInputElement::HasCachedSelection() {
6992 TextControlState* state = GetEditorState();
6993 if (!state) {
6994 return false;
6996 return state->IsSelectionCached() && state->HasNeverInitializedBefore() &&
6997 state->GetSelectionProperties().GetStart() !=
6998 state->GetSelectionProperties().GetEnd();
7001 void HTMLInputElement::SetRevealPassword(bool aValue) {
7002 if (NS_WARN_IF(mType != FormControlType::InputPassword)) {
7003 return;
7005 if (aValue == State().HasState(ElementState::REVEALED)) {
7006 return;
7008 RefPtr doc = OwnerDoc();
7009 // We allow chrome code to prevent this. This is important for about:logins,
7010 // which may need to run some OS-dependent authentication code before
7011 // revealing the saved passwords.
7012 bool defaultAction = true;
7013 nsContentUtils::DispatchEventOnlyToChrome(
7014 doc, this, u"MozWillToggleReveal"_ns, CanBubble::eYes, Cancelable::eYes,
7015 &defaultAction);
7016 if (NS_WARN_IF(!defaultAction)) {
7017 return;
7019 SetStates(ElementState::REVEALED, aValue);
7022 bool HTMLInputElement::RevealPassword() const {
7023 if (NS_WARN_IF(mType != FormControlType::InputPassword)) {
7024 return false;
7026 return State().HasState(ElementState::REVEALED);
7029 void HTMLInputElement::FieldSetDisabledChanged(bool aNotify) {
7030 // This *has* to be called *before* UpdateBarredFromConstraintValidation and
7031 // UpdateValueMissingValidityState because these two functions depend on our
7032 // disabled state.
7033 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify);
7035 UpdateValueMissingValidityState();
7036 UpdateBarredFromConstraintValidation();
7037 UpdateValidityElementStates(aNotify);
7040 void HTMLInputElement::SetFilePickerFiltersFromAccept(
7041 nsIFilePicker* filePicker) {
7042 // We always add |filterAll|
7043 filePicker->AppendFilters(nsIFilePicker::filterAll);
7045 NS_ASSERTION(HasAttr(nsGkAtoms::accept),
7046 "You should not call SetFilePickerFiltersFromAccept if the"
7047 " element has no accept attribute!");
7049 // Services to retrieve image/*, audio/*, video/* filters
7050 nsCOMPtr<nsIStringBundleService> stringService =
7051 components::StringBundle::Service();
7052 if (!stringService) {
7053 return;
7055 nsCOMPtr<nsIStringBundle> filterBundle;
7056 if (NS_FAILED(stringService->CreateBundle(
7057 "chrome://global/content/filepicker.properties",
7058 getter_AddRefs(filterBundle)))) {
7059 return;
7062 // Service to retrieve mime type information for mime types filters
7063 nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
7064 if (!mimeService) {
7065 return;
7068 nsAutoString accept;
7069 GetAttr(nsGkAtoms::accept, accept);
7071 HTMLSplitOnSpacesTokenizer tokenizer(accept, ',');
7073 nsTArray<nsFilePickerFilter> filters;
7074 nsString allExtensionsList;
7076 // Retrieve all filters
7077 while (tokenizer.hasMoreTokens()) {
7078 const nsDependentSubstring& token = tokenizer.nextToken();
7080 if (token.IsEmpty()) {
7081 continue;
7084 int32_t filterMask = 0;
7085 nsString filterName;
7086 nsString extensionListStr;
7088 // First, check for image/audio/video filters...
7089 if (token.EqualsLiteral("image/*")) {
7090 filterMask = nsIFilePicker::filterImages;
7091 filterBundle->GetStringFromName("imageFilter", extensionListStr);
7092 } else if (token.EqualsLiteral("audio/*")) {
7093 filterMask = nsIFilePicker::filterAudio;
7094 filterBundle->GetStringFromName("audioFilter", extensionListStr);
7095 } else if (token.EqualsLiteral("video/*")) {
7096 filterMask = nsIFilePicker::filterVideo;
7097 filterBundle->GetStringFromName("videoFilter", extensionListStr);
7098 } else if (token.First() == '.') {
7099 if (token.Contains(';') || token.Contains('*')) {
7100 // Ignore this filter as it contains reserved characters
7101 continue;
7103 extensionListStr = u"*"_ns + token;
7104 filterName = extensionListStr;
7105 } else {
7106 //... if no image/audio/video filter is found, check mime types filters
7107 nsCOMPtr<nsIMIMEInfo> mimeInfo;
7108 if (NS_FAILED(
7109 mimeService->GetFromTypeAndExtension(NS_ConvertUTF16toUTF8(token),
7110 ""_ns, // No extension
7111 getter_AddRefs(mimeInfo))) ||
7112 !mimeInfo) {
7113 continue;
7116 // Get a name for the filter: first try the description, then the mime
7117 // type name if there is no description
7118 mimeInfo->GetDescription(filterName);
7119 if (filterName.IsEmpty()) {
7120 nsCString mimeTypeName;
7121 mimeInfo->GetType(mimeTypeName);
7122 CopyUTF8toUTF16(mimeTypeName, filterName);
7125 // Get extension list
7126 nsCOMPtr<nsIUTF8StringEnumerator> extensions;
7127 mimeInfo->GetFileExtensions(getter_AddRefs(extensions));
7129 bool hasMore;
7130 while (NS_SUCCEEDED(extensions->HasMore(&hasMore)) && hasMore) {
7131 nsCString extension;
7132 if (NS_FAILED(extensions->GetNext(extension))) {
7133 continue;
7135 if (!extensionListStr.IsEmpty()) {
7136 extensionListStr.AppendLiteral("; ");
7138 extensionListStr += u"*."_ns + NS_ConvertUTF8toUTF16(extension);
7142 if (!filterMask && (extensionListStr.IsEmpty() || filterName.IsEmpty())) {
7143 // No valid filter found
7144 continue;
7147 // At this point we're sure the token represents a valid filter, so pass
7148 // it directly as a raw filter.
7149 filePicker->AppendRawFilter(token);
7151 // If we arrived here, that means we have a valid filter: let's create it
7152 // and add it to our list, if no similar filter is already present
7153 nsFilePickerFilter filter;
7154 if (filterMask) {
7155 filter = nsFilePickerFilter(filterMask);
7156 } else {
7157 filter = nsFilePickerFilter(filterName, extensionListStr);
7160 if (!filters.Contains(filter)) {
7161 if (!allExtensionsList.IsEmpty()) {
7162 allExtensionsList.AppendLiteral("; ");
7164 allExtensionsList += extensionListStr;
7165 filters.AppendElement(filter);
7169 // Remove similar filters
7170 // Iterate over a copy, as we might modify the original filters list
7171 const nsTArray<nsFilePickerFilter> filtersCopy = filters.Clone();
7172 for (uint32_t i = 0; i < filtersCopy.Length(); ++i) {
7173 const nsFilePickerFilter& filterToCheck = filtersCopy[i];
7174 if (filterToCheck.mFilterMask) {
7175 continue;
7177 for (uint32_t j = 0; j < filtersCopy.Length(); ++j) {
7178 if (i == j) {
7179 continue;
7181 // Check if this filter's extension list is a substring of the other one.
7182 // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
7183 // be removed.
7184 // Add an extra "; " to be sure the check will work and avoid cases like
7185 // "*.xls" being a subtring of "*.xslx" while those are two differents
7186 // filters and none should be removed.
7187 if (FindInReadable(filterToCheck.mFilter + u";"_ns,
7188 filtersCopy[j].mFilter + u";"_ns)) {
7189 // We already have a similar, less restrictive filter (i.e.
7190 // filterToCheck extensionList is just a subset of another filter
7191 // extension list): remove this one
7192 filters.RemoveElement(filterToCheck);
7197 // Add "All Supported Types" filter
7198 if (filters.Length() > 1) {
7199 nsAutoString title;
7200 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
7201 "AllSupportedTypes", title);
7202 filePicker->AppendFilter(title, allExtensionsList);
7205 // Add each filter
7206 for (uint32_t i = 0; i < filters.Length(); ++i) {
7207 const nsFilePickerFilter& filter = filters[i];
7208 if (filter.mFilterMask) {
7209 filePicker->AppendFilters(filter.mFilterMask);
7210 } else {
7211 filePicker->AppendFilter(filter.mTitle, filter.mFilter);
7215 if (filters.Length() >= 1) {
7216 // |filterAll| will always use index=0 so we need to set index=1 as the
7217 // current filter. This will be "All Supported Types" for multiple filters.
7218 filePicker->SetFilterIndex(1);
7222 Decimal HTMLInputElement::GetStepScaleFactor() const {
7223 MOZ_ASSERT(DoesStepApply());
7225 switch (mType) {
7226 case FormControlType::InputDate:
7227 return kStepScaleFactorDate;
7228 case FormControlType::InputNumber:
7229 case FormControlType::InputRange:
7230 return kStepScaleFactorNumberRange;
7231 case FormControlType::InputTime:
7232 case FormControlType::InputDatetimeLocal:
7233 return kStepScaleFactorTime;
7234 case FormControlType::InputMonth:
7235 return kStepScaleFactorMonth;
7236 case FormControlType::InputWeek:
7237 return kStepScaleFactorWeek;
7238 default:
7239 MOZ_ASSERT(false, "Unrecognized input type");
7240 return Decimal::nan();
7244 Decimal HTMLInputElement::GetDefaultStep() const {
7245 MOZ_ASSERT(DoesStepApply());
7247 switch (mType) {
7248 case FormControlType::InputDate:
7249 case FormControlType::InputMonth:
7250 case FormControlType::InputWeek:
7251 case FormControlType::InputNumber:
7252 case FormControlType::InputRange:
7253 return kDefaultStep;
7254 case FormControlType::InputTime:
7255 case FormControlType::InputDatetimeLocal:
7256 return kDefaultStepTime;
7257 default:
7258 MOZ_ASSERT(false, "Unrecognized input type");
7259 return Decimal::nan();
7263 void HTMLInputElement::SetUserInteracted(bool aInteracted) {
7264 if (mUserInteracted == aInteracted) {
7265 return;
7267 mUserInteracted = aInteracted;
7268 UpdateValidityElementStates(true);
7271 void HTMLInputElement::UpdateInRange(bool aNotify) {
7272 AutoStateChangeNotifier notifier(*this, aNotify);
7273 RemoveStatesSilently(ElementState::INRANGE | ElementState::OUTOFRANGE);
7274 if (!mHasRange || !IsCandidateForConstraintValidation()) {
7275 return;
7277 bool outOfRange = GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
7278 GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW);
7279 AddStatesSilently(outOfRange ? ElementState::OUTOFRANGE
7280 : ElementState::INRANGE);
7283 void HTMLInputElement::UpdateHasRange(bool aNotify) {
7284 // There is a range if min/max applies for the type and if the element
7285 // currently have a valid min or max.
7286 const bool newHasRange = [&] {
7287 if (!DoesMinMaxApply()) {
7288 return false;
7290 return !GetMinimum().isNaN() || !GetMaximum().isNaN();
7291 }();
7293 if (newHasRange == mHasRange) {
7294 return;
7297 mHasRange = newHasRange;
7298 UpdateInRange(aNotify);
7301 void HTMLInputElement::PickerClosed() { mPickerRunning = false; }
7303 JSObject* HTMLInputElement::WrapNode(JSContext* aCx,
7304 JS::Handle<JSObject*> aGivenProto) {
7305 return HTMLInputElement_Binding::Wrap(aCx, this, aGivenProto);
7308 GetFilesHelper* HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag,
7309 ErrorResult& aRv) {
7310 MOZ_ASSERT(mFileData);
7312 if (aRecursiveFlag) {
7313 if (!mFileData->mGetFilesRecursiveHelper) {
7314 mFileData->mGetFilesRecursiveHelper = GetFilesHelper::Create(
7315 GetFilesOrDirectoriesInternal(), aRecursiveFlag, aRv);
7316 if (NS_WARN_IF(aRv.Failed())) {
7317 return nullptr;
7321 return mFileData->mGetFilesRecursiveHelper;
7324 if (!mFileData->mGetFilesNonRecursiveHelper) {
7325 mFileData->mGetFilesNonRecursiveHelper = GetFilesHelper::Create(
7326 GetFilesOrDirectoriesInternal(), aRecursiveFlag, aRv);
7327 if (NS_WARN_IF(aRv.Failed())) {
7328 return nullptr;
7332 return mFileData->mGetFilesNonRecursiveHelper;
7335 void HTMLInputElement::UpdateEntries(
7336 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories) {
7337 MOZ_ASSERT(mFileData && mFileData->mEntries.IsEmpty());
7339 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
7340 MOZ_ASSERT(global);
7342 RefPtr<FileSystem> fs = FileSystem::Create(global);
7343 if (NS_WARN_IF(!fs)) {
7344 return;
7347 Sequence<RefPtr<FileSystemEntry>> entries;
7348 for (uint32_t i = 0; i < aFilesOrDirectories.Length(); ++i) {
7349 RefPtr<FileSystemEntry> entry =
7350 FileSystemEntry::Create(global, aFilesOrDirectories[i], fs);
7351 MOZ_ASSERT(entry);
7353 if (!entries.AppendElement(entry, fallible)) {
7354 return;
7358 // The root fileSystem is a DirectoryEntry object that contains only the
7359 // dropped fileEntry and directoryEntry objects.
7360 fs->CreateRoot(entries);
7362 mFileData->mEntries = std::move(entries);
7365 void HTMLInputElement::GetWebkitEntries(
7366 nsTArray<RefPtr<FileSystemEntry>>& aSequence) {
7367 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
7368 return;
7371 Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED, true);
7372 aSequence.AppendElements(mFileData->mEntries);
7375 already_AddRefed<nsINodeList> HTMLInputElement::GetLabels() {
7376 if (!IsLabelable()) {
7377 return nullptr;
7380 return nsGenericHTMLElement::Labels();
7383 void HTMLInputElement::MaybeFireInputPasswordRemoved() {
7384 // We want this event to be fired only when the password field is removed
7385 // from the DOM tree, not when it is released (ex, tab is closed). So don't
7386 // fire an event when the password input field doesn't have a docshell.
7387 Document* doc = GetComposedDoc();
7388 nsIDocShell* container = doc ? doc->GetDocShell() : nullptr;
7389 if (!container) {
7390 return;
7393 // Right now, only the password manager listens to the event and only listen
7394 // to it under certain circumstances. So don't fire this event unless
7395 // necessary.
7396 if (!doc->ShouldNotifyFormOrPasswordRemoved()) {
7397 return;
7400 AsyncEventDispatcher::RunDOMEventWhenSafe(
7401 *this, u"DOMInputPasswordRemoved"_ns, CanBubble::eNo,
7402 ChromeOnlyDispatch::eYes);
7405 } // namespace mozilla::dom
7407 #undef NS_ORIGINAL_CHECKED_VALUE