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/. */
6 
7 #include "mozilla/dom/HTMLInputElement.h"
8 
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/GetFilesHelper.h"
21 #include "mozilla/dom/HTMLFormSubmission.h"
22 #include "mozilla/dom/WindowContext.h"
23 #include "mozilla/dom/InputType.h"
24 #include "mozilla/dom/UserActivation.h"
25 #include "mozilla/dom/MutationEventBinding.h"
26 #include "mozilla/dom/WheelEventBinding.h"
27 #include "mozilla/PresShell.h"
28 #include "mozilla/StaticPrefs_dom.h"
29 #include "mozilla/TextUtils.h"
30 #include "nsAttrValueInlines.h"
31 #include "nsCRTGlue.h"
32 #include "nsNetUtil.h"
33 #include "nsQueryObject.h"
34 
35 #include "nsIRadioVisitor.h"
36 
37 #include "HTMLFormSubmissionConstants.h"
38 #include "mozilla/Telemetry.h"
39 #include "nsBaseCommandController.h"
40 #include "nsIStringBundle.h"
41 #include "nsFocusManager.h"
42 #include "nsColorControlFrame.h"
43 #include "nsNumberControlFrame.h"
44 #include "nsSearchControlFrame.h"
45 #include "nsPIDOMWindow.h"
46 #include "nsRepeatService.h"
47 #include "nsContentCID.h"
48 #include "mozilla/dom/ProgressEvent.h"
49 #include "nsGkAtoms.h"
50 #include "nsStyleConsts.h"
51 #include "nsPresContext.h"
52 #include "nsMappedAttributes.h"
53 #include "nsIFormControl.h"
54 #include "mozilla/dom/Document.h"
55 #include "nsIFormControlFrame.h"
56 #include "nsITextControlFrame.h"
57 #include "nsIFrame.h"
58 #include "nsRangeFrame.h"
59 #include "nsError.h"
60 #include "nsIEditor.h"
61 #include "nsAttrValueOrString.h"
62 #include "nsIPromptCollection.h"
63 
64 #include "mozilla/PresState.h"
65 #include "nsLinebreakConverter.h"  //to strip out carriage returns
66 #include "nsReadableUtils.h"
67 #include "nsUnicharUtils.h"
68 #include "nsLayoutUtils.h"
69 #include "nsVariant.h"
70 
71 #include "mozilla/ContentEvents.h"
72 #include "mozilla/EventDispatcher.h"
73 #include "mozilla/EventStates.h"
74 #include "mozilla/MappedDeclarations.h"
75 #include "mozilla/InternalMutationEvent.h"
76 #include "mozilla/TextControlState.h"
77 #include "mozilla/TextEditor.h"
78 #include "mozilla/TextEvents.h"
79 #include "mozilla/TouchEvents.h"
80 
81 #include <algorithm>
82 
83 // input type=radio
84 #include "nsIRadioGroupContainer.h"
85 
86 // input type=file
87 #include "mozilla/dom/FileSystemEntry.h"
88 #include "mozilla/dom/FileSystem.h"
89 #include "mozilla/dom/File.h"
90 #include "mozilla/dom/FileList.h"
91 #include "nsIFile.h"
92 #include "nsDirectoryServiceDefs.h"
93 #include "nsIContentPrefService2.h"
94 #include "nsIMIMEService.h"
95 #include "nsIObserverService.h"
96 #include "nsGlobalWindow.h"
97 
98 // input type=image
99 #include "nsImageLoadingContent.h"
100 #include "imgRequestProxy.h"
101 
102 #include "mozAutoDocUpdate.h"
103 #include "nsContentCreatorFunctions.h"
104 #include "nsContentUtils.h"
105 #include "mozilla/dom/DirectionalityUtils.h"
106 #include "nsRadioVisitor.h"
107 
108 #include "mozilla/LookAndFeel.h"
109 #include "mozilla/Preferences.h"
110 #include "mozilla/MathAlgorithms.h"
111 #include "mozilla/TextUtils.h"
112 
113 #include <limits>
114 
115 #include "nsIColorPicker.h"
116 #include "nsIStringEnumerator.h"
117 #include "HTMLSplitOnSpacesTokenizer.h"
118 #include "nsIMIMEInfo.h"
119 #include "nsFrameSelection.h"
120 #include "nsBaseCommandController.h"
121 #include "nsXULControllers.h"
122 
123 // input type=date
124 #include "js/Date.h"
125 
126 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
127 
128 // XXX align=left, hspace, vspace, border? other nav4 attrs
129 
130 namespace mozilla::dom {
131 
132 // First bits are needed for the control type.
133 #define NS_OUTER_ACTIVATE_EVENT (1 << 9)
134 #define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
135 // (1 << 11 is unused)
136 #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
137 #define NS_PRE_HANDLE_BLUR_EVENT (1 << 13)
138 #define NS_IN_SUBMIT_CLICK (1 << 15)
139 #define NS_CONTROL_TYPE(bits)                                              \
140   ((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE |        \
141               NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \
142               NS_IN_SUBMIT_CLICK))
143 
144 // whether textfields should be selected once focused:
145 //  -1: no, 1: yes, 0: uninitialized
146 static int32_t gSelectTextFieldOnFocus;
147 UploadLastDir* HTMLInputElement::gUploadLastDir;
148 
149 static const nsAttrValue::EnumTable kInputTypeTable[] = {
150     {"button", FormControlType::InputButton},
151     {"checkbox", FormControlType::InputCheckbox},
152     {"color", FormControlType::InputColor},
153     {"date", FormControlType::InputDate},
154     {"datetime-local", FormControlType::InputDatetimeLocal},
155     {"email", FormControlType::InputEmail},
156     {"file", FormControlType::InputFile},
157     {"hidden", FormControlType::InputHidden},
158     {"reset", FormControlType::InputReset},
159     {"image", FormControlType::InputImage},
160     {"month", FormControlType::InputMonth},
161     {"number", FormControlType::InputNumber},
162     {"password", FormControlType::InputPassword},
163     {"radio", FormControlType::InputRadio},
164     {"range", FormControlType::InputRange},
165     {"search", FormControlType::InputSearch},
166     {"submit", FormControlType::InputSubmit},
167     {"tel", FormControlType::InputTel},
168     {"time", FormControlType::InputTime},
169     {"url", FormControlType::InputUrl},
170     {"week", FormControlType::InputWeek},
171     // "text" must be last for ParseAttribute to work right.  If you add things
172     // before it, please update kInputDefaultType.
173     {"text", FormControlType::InputText},
174     {nullptr, 0}};
175 
176 // Default type is 'text'.
177 static const nsAttrValue::EnumTable* kInputDefaultType =
178     &kInputTypeTable[ArrayLength(kInputTypeTable) - 2];
179 
180 static const nsAttrValue::EnumTable kCaptureTable[] = {
181     {"user", static_cast<int16_t>(nsIFilePicker::captureUser)},
182     {"environment", static_cast<int16_t>(nsIFilePicker::captureEnv)},
183     {"", static_cast<int16_t>(nsIFilePicker::captureDefault)},
184     {nullptr, static_cast<int16_t>(nsIFilePicker::captureNone)}};
185 
186 static const nsAttrValue::EnumTable* kCaptureDefault = &kCaptureTable[2];
187 
188 const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000);
189 const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
190 const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
191 const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1);
192 const Decimal HTMLInputElement::kStepScaleFactorWeek = Decimal(7 * 86400000);
193 const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
194 const Decimal HTMLInputElement::kDefaultStepBaseWeek = Decimal(-259200000);
195 const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
196 const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
197 const Decimal HTMLInputElement::kStepAny = Decimal(0);
198 
199 const double HTMLInputElement::kMinimumYear = 1;
200 const double HTMLInputElement::kMaximumYear = 275760;
201 const double HTMLInputElement::kMaximumWeekInMaximumYear = 37;
202 const double HTMLInputElement::kMaximumDayInMaximumYear = 13;
203 const double HTMLInputElement::kMaximumMonthInMaximumYear = 9;
204 const double HTMLInputElement::kMaximumWeekInYear = 53;
205 const double HTMLInputElement::kMsPerDay = 24 * 60 * 60 * 1000;
206 
207 // An helper class for the dispatching of the 'change' event.
208 // This class is used when the FilePicker finished its task (or when files and
209 // directories are set by some chrome/test only method).
210 // The task of this class is to postpone the dispatching of 'change' and 'input'
211 // events at the end of the exploration of the directories.
212 class DispatchChangeEventCallback final : public GetFilesCallback {
213  public:
DispatchChangeEventCallback(HTMLInputElement * aInputElement)214   explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
215       : mInputElement(aInputElement) {
216     MOZ_ASSERT(aInputElement);
217   }
218 
Callback(nsresult aStatus,const FallibleTArray<RefPtr<BlobImpl>> & aBlobImpls)219   virtual void Callback(
220       nsresult aStatus,
221       const FallibleTArray<RefPtr<BlobImpl>>& aBlobImpls) override {
222     if (!mInputElement->GetOwnerGlobal()) {
223       return;
224     }
225 
226     nsTArray<OwningFileOrDirectory> array;
227     for (uint32_t i = 0; i < aBlobImpls.Length(); ++i) {
228       OwningFileOrDirectory* element = array.AppendElement();
229       RefPtr<File> file =
230           File::Create(mInputElement->GetOwnerGlobal(), aBlobImpls[i]);
231       if (NS_WARN_IF(!file)) {
232         return;
233       }
234 
235       element->SetAsFile() = file;
236     }
237 
238     mInputElement->SetFilesOrDirectories(array, true);
239     Unused << NS_WARN_IF(NS_FAILED(DispatchEvents()));
240   }
241 
242   MOZ_CAN_RUN_SCRIPT_BOUNDARY
DispatchEvents()243   nsresult DispatchEvents() {
244     RefPtr<HTMLInputElement> inputElement(mInputElement);
245     nsresult rv = nsContentUtils::DispatchInputEvent(inputElement);
246     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch input event");
247 
248     rv = nsContentUtils::DispatchTrustedEvent(
249         mInputElement->OwnerDoc(), static_cast<Element*>(mInputElement.get()),
250         u"change"_ns, CanBubble::eYes, Cancelable::eNo);
251 
252     return rv;
253   }
254 
255  private:
256   RefPtr<HTMLInputElement> mInputElement;
257 };
258 
259 struct HTMLInputElement::FileData {
260   /**
261    * The value of the input if it is a file input. This is the list of files or
262    * directories DOM objects used when uploading a file. It is vital that this
263    * is kept separate from mValue so that it won't be possible to 'leak' the
264    * value from a text-input to a file-input. Additionally, the logic for this
265    * value is kept as simple as possible to avoid accidental errors where the
266    * wrong filename is used.  Therefor the list of filenames is always owned by
267    * this member, never by the frame. Whenever the frame wants to change the
268    * filename it has to call SetFilesOrDirectories to update this member.
269    */
270   nsTArray<OwningFileOrDirectory> mFilesOrDirectories;
271 
272   RefPtr<GetFilesHelper> mGetFilesRecursiveHelper;
273   RefPtr<GetFilesHelper> mGetFilesNonRecursiveHelper;
274 
275   /**
276    * Hack for bug 1086684: Stash the .value when we're a file picker.
277    */
278   nsString mFirstFilePath;
279 
280   RefPtr<FileList> mFileList;
281   Sequence<RefPtr<FileSystemEntry>> mEntries;
282 
283   nsString mStaticDocFileList;
284 
ClearGetFilesHelpersmozilla::dom::HTMLInputElement::FileData285   void ClearGetFilesHelpers() {
286     if (mGetFilesRecursiveHelper) {
287       mGetFilesRecursiveHelper->Unlink();
288       mGetFilesRecursiveHelper = nullptr;
289     }
290 
291     if (mGetFilesNonRecursiveHelper) {
292       mGetFilesNonRecursiveHelper->Unlink();
293       mGetFilesNonRecursiveHelper = nullptr;
294     }
295   }
296 
297   // Cycle Collection support.
Traversemozilla::dom::HTMLInputElement::FileData298   void Traverse(nsCycleCollectionTraversalCallback& cb) {
299     FileData* tmp = this;
300     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
301     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
302     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries)
303     if (mGetFilesRecursiveHelper) {
304       mGetFilesRecursiveHelper->Traverse(cb);
305     }
306 
307     if (mGetFilesNonRecursiveHelper) {
308       mGetFilesNonRecursiveHelper->Traverse(cb);
309     }
310   }
311 
Unlinkmozilla::dom::HTMLInputElement::FileData312   void Unlink() {
313     FileData* tmp = this;
314     NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
315     NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
316     NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries)
317     ClearGetFilesHelpers();
318   }
319 };
320 
nsFilePickerShownCallback(HTMLInputElement * aInput,nsIFilePicker * aFilePicker)321 HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
322     HTMLInputElement* aInput, nsIFilePicker* aFilePicker)
323     : mFilePicker(aFilePicker), mInput(aInput) {}
324 
NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback,nsIContentPrefCallback2)325 NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2)
326 
327 NS_IMETHODIMP
328 UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason) {
329   nsCOMPtr<nsIFile> localFile;
330   nsAutoString prefStr;
331 
332   if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) {
333     Preferences::GetString("dom.input.fallbackUploadDir", prefStr);
334   }
335 
336   if (prefStr.IsEmpty() && mResult) {
337     nsCOMPtr<nsIVariant> pref;
338     mResult->GetValue(getter_AddRefs(pref));
339     pref->GetAsAString(prefStr);
340   }
341 
342   if (!prefStr.IsEmpty()) {
343     localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
344     if (localFile && NS_WARN_IF(NS_FAILED(localFile->InitWithPath(prefStr)))) {
345       localFile = nullptr;
346     }
347   }
348 
349   if (localFile) {
350     mFilePicker->SetDisplayDirectory(localFile);
351   } else {
352     // If no custom directory was set through the pref, default to
353     // "desktop" directory for each platform.
354     mFilePicker->SetDisplaySpecialDirectory(
355         NS_LITERAL_STRING_FROM_CSTRING(NS_OS_DESKTOP_DIR));
356   }
357 
358   mFilePicker->Open(mFpCallback);
359   return NS_OK;
360 }
361 
362 NS_IMETHODIMP
HandleResult(nsIContentPref * pref)363 UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref* pref) {
364   mResult = pref;
365   return NS_OK;
366 }
367 
368 NS_IMETHODIMP
HandleError(nsresult error)369 UploadLastDir::ContentPrefCallback::HandleError(nsresult error) {
370   // HandleCompletion is always called (even with HandleError was called),
371   // so we don't need to do anything special here.
372   return NS_OK;
373 }
374 
375 namespace {
376 
377 /**
378  * This may return nullptr if the DOM File's implementation of
379  * File::mozFullPathInternal does not successfully return a non-empty
380  * string that is a valid path. This can happen on Firefox OS, for example,
381  * where the file picker can create Blobs.
382  */
LastUsedDirectory(const OwningFileOrDirectory & aData)383 static already_AddRefed<nsIFile> LastUsedDirectory(
384     const OwningFileOrDirectory& aData) {
385   if (aData.IsFile()) {
386     nsAutoString path;
387     ErrorResult error;
388     aData.GetAsFile()->GetMozFullPathInternal(path, error);
389     if (error.Failed() || path.IsEmpty()) {
390       error.SuppressException();
391       return nullptr;
392     }
393 
394     nsCOMPtr<nsIFile> localFile;
395     nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(localFile));
396     if (NS_WARN_IF(NS_FAILED(rv))) {
397       return nullptr;
398     }
399 
400     nsCOMPtr<nsIFile> parentFile;
401     rv = localFile->GetParent(getter_AddRefs(parentFile));
402     if (NS_WARN_IF(NS_FAILED(rv))) {
403       return nullptr;
404     }
405 
406     return parentFile.forget();
407   }
408 
409   MOZ_ASSERT(aData.IsDirectory());
410 
411   nsCOMPtr<nsIFile> localFile = aData.GetAsDirectory()->GetInternalNsIFile();
412   MOZ_ASSERT(localFile);
413 
414   return localFile.forget();
415 }
416 
GetDOMFileOrDirectoryName(const OwningFileOrDirectory & aData,nsAString & aName)417 void GetDOMFileOrDirectoryName(const OwningFileOrDirectory& aData,
418                                nsAString& aName) {
419   if (aData.IsFile()) {
420     aData.GetAsFile()->GetName(aName);
421   } else {
422     MOZ_ASSERT(aData.IsDirectory());
423     ErrorResult rv;
424     aData.GetAsDirectory()->GetName(aName, rv);
425     if (NS_WARN_IF(rv.Failed())) {
426       rv.SuppressException();
427     }
428   }
429 }
430 
GetDOMFileOrDirectoryPath(const OwningFileOrDirectory & aData,nsAString & aPath,ErrorResult & aRv)431 void GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
432                                nsAString& aPath, ErrorResult& aRv) {
433   if (aData.IsFile()) {
434     aData.GetAsFile()->GetMozFullPathInternal(aPath, aRv);
435   } else {
436     MOZ_ASSERT(aData.IsDirectory());
437     aData.GetAsDirectory()->GetFullRealPath(aPath);
438   }
439 }
440 
441 }  // namespace
442 
443 NS_IMETHODIMP
Done(int16_t aResult)444 HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult) {
445   mInput->PickerClosed();
446 
447   if (aResult == nsIFilePicker::returnCancel) {
448     RefPtr<HTMLInputElement> inputElement(mInput);
449     return nsContentUtils::DispatchTrustedEvent(
450         inputElement->OwnerDoc(), static_cast<Element*>(inputElement.get()),
451         u"cancel"_ns, CanBubble::eYes, Cancelable::eNo);
452   }
453 
454   int16_t mode;
455   mFilePicker->GetMode(&mode);
456 
457   // Collect new selected filenames
458   nsTArray<OwningFileOrDirectory> newFilesOrDirectories;
459   if (mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple)) {
460     nsCOMPtr<nsISimpleEnumerator> iter;
461     nsresult rv =
462         mFilePicker->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter));
463     NS_ENSURE_SUCCESS(rv, rv);
464 
465     if (!iter) {
466       return NS_OK;
467     }
468 
469     nsCOMPtr<nsISupports> tmp;
470     bool hasMore = true;
471 
472     while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
473       iter->GetNext(getter_AddRefs(tmp));
474       RefPtr<Blob> domBlob = do_QueryObject(tmp);
475       MOZ_ASSERT(domBlob,
476                  "Null file object from FilePicker's file enumerator?");
477       if (!domBlob) {
478         continue;
479       }
480 
481       OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
482       element->SetAsFile() = domBlob->ToFile();
483     }
484   } else {
485     MOZ_ASSERT(mode == static_cast<int16_t>(nsIFilePicker::modeOpen) ||
486                mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder));
487     nsCOMPtr<nsISupports> tmp;
488     nsresult rv = mFilePicker->GetDomFileOrDirectory(getter_AddRefs(tmp));
489     NS_ENSURE_SUCCESS(rv, rv);
490 
491     // Show a prompt to get user confirmation before allowing folder access.
492     // This is to prevent sites from tricking the user into uploading files.
493     // See Bug 1338637.
494     if (mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder)) {
495       nsCOMPtr<nsIPromptCollection> prompter =
496           do_GetService("@mozilla.org/embedcomp/prompt-collection;1");
497       if (!prompter) {
498         return NS_ERROR_NOT_AVAILABLE;
499       }
500 
501       bool confirmed = false;
502       BrowsingContext* bc = mInput->OwnerDoc()->GetBrowsingContext();
503 
504       // Get directory name
505       RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
506       nsAutoString directoryName;
507       ErrorResult error;
508       directory->GetName(directoryName, error);
509       if (NS_WARN_IF(error.Failed())) {
510         return error.StealNSResult();
511       }
512 
513       rv = prompter->ConfirmFolderUpload(bc, directoryName, &confirmed);
514       NS_ENSURE_SUCCESS(rv, rv);
515       if (!confirmed) {
516         // User aborted upload
517         return NS_OK;
518       }
519     }
520 
521     RefPtr<Blob> blob = do_QueryObject(tmp);
522     if (blob) {
523       RefPtr<File> file = blob->ToFile();
524       MOZ_ASSERT(file);
525 
526       OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
527       element->SetAsFile() = file;
528     } else if (tmp) {
529       RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
530       OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
531       element->SetAsDirectory() = directory;
532     }
533   }
534 
535   if (newFilesOrDirectories.IsEmpty()) {
536     return NS_OK;
537   }
538 
539   // Store the last used directory using the content pref service:
540   nsCOMPtr<nsIFile> lastUsedDir = LastUsedDirectory(newFilesOrDirectories[0]);
541 
542   if (lastUsedDir) {
543     HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(mInput->OwnerDoc(),
544                                                              lastUsedDir);
545   }
546 
547   // The text control frame (if there is one) isn't going to send a change
548   // event because it will think this is done by a script.
549   // So, we can safely send one by ourself.
550   mInput->SetFilesOrDirectories(newFilesOrDirectories, true);
551 
552   // mInput(HTMLInputElement) has no scriptGlobalObject, don't create
553   // DispatchChangeEventCallback
554   if (!mInput->GetOwnerGlobal()) {
555     return NS_OK;
556   }
557   RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
558       new DispatchChangeEventCallback(mInput);
559 
560   if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
561       mInput->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
562     ErrorResult error;
563     GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
564     if (NS_WARN_IF(error.Failed())) {
565       return error.StealNSResult();
566     }
567 
568     helper->AddCallback(dispatchChangeEventCallback);
569     return NS_OK;
570   }
571 
572   return dispatchChangeEventCallback->DispatchEvents();
573 }
574 
575 NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback,
576                   nsIFilePickerShownCallback)
577 
578 class nsColorPickerShownCallback final : public nsIColorPickerShownCallback {
579   ~nsColorPickerShownCallback() = default;
580 
581  public:
nsColorPickerShownCallback(HTMLInputElement * aInput,nsIColorPicker * aColorPicker)582   nsColorPickerShownCallback(HTMLInputElement* aInput,
583                              nsIColorPicker* aColorPicker)
584       : mInput(aInput), mColorPicker(aColorPicker), mValueChanged(false) {}
585 
586   NS_DECL_ISUPPORTS
587 
588   MOZ_CAN_RUN_SCRIPT_BOUNDARY
589   NS_IMETHOD Update(const nsAString& aColor) override;
590   MOZ_CAN_RUN_SCRIPT_BOUNDARY
591   NS_IMETHOD Done(const nsAString& aColor) override;
592 
593  private:
594   /**
595    * Updates the internals of the object using aColor as the new value.
596    * If aTrustedUpdate is true, it will consider that aColor is a new value.
597    * Otherwise, it will check that aColor is different from the current value.
598    */
599   MOZ_CAN_RUN_SCRIPT
600   nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
601 
602   RefPtr<HTMLInputElement> mInput;
603   nsCOMPtr<nsIColorPicker> mColorPicker;
604   bool mValueChanged;
605 };
606 
UpdateInternal(const nsAString & aColor,bool aTrustedUpdate)607 nsresult nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
608                                                     bool aTrustedUpdate) {
609   bool valueChanged = false;
610 
611   nsAutoString oldValue;
612   if (aTrustedUpdate) {
613     valueChanged = true;
614   } else {
615     mInput->GetValue(oldValue, CallerType::System);
616   }
617 
618   mInput->SetValue(aColor, CallerType::System, IgnoreErrors());
619 
620   if (!aTrustedUpdate) {
621     nsAutoString newValue;
622     mInput->GetValue(newValue, CallerType::System);
623     if (!oldValue.Equals(newValue)) {
624       valueChanged = true;
625     }
626   }
627 
628   if (!valueChanged) {
629     return NS_OK;
630   }
631 
632   mValueChanged = true;
633   RefPtr<HTMLInputElement> input(mInput);
634   DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(input);
635   NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
636                        "Failed to dispatch input event");
637   return NS_OK;
638 }
639 
640 NS_IMETHODIMP
Update(const nsAString & aColor)641 nsColorPickerShownCallback::Update(const nsAString& aColor) {
642   return UpdateInternal(aColor, true);
643 }
644 
645 NS_IMETHODIMP
Done(const nsAString & aColor)646 nsColorPickerShownCallback::Done(const nsAString& aColor) {
647   /**
648    * When Done() is called, we might be at the end of a serie of Update() calls
649    * in which case mValueChanged is set to true and a change event will have to
650    * be fired but we might also be in a one shot Done() call situation in which
651    * case we should fire a change event iif the value actually changed.
652    * UpdateInternal(bool) is taking care of that logic for us.
653    */
654   nsresult rv = NS_OK;
655 
656   mInput->PickerClosed();
657 
658   if (!aColor.IsEmpty()) {
659     UpdateInternal(aColor, false);
660   }
661 
662   if (mValueChanged) {
663     rv = nsContentUtils::DispatchTrustedEvent(
664         mInput->OwnerDoc(), static_cast<Element*>(mInput.get()), u"change"_ns,
665         CanBubble::eYes, Cancelable::eNo);
666   }
667 
668   return rv;
669 }
670 
NS_IMPL_ISUPPORTS(nsColorPickerShownCallback,nsIColorPickerShownCallback)671 NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback)
672 
673 static bool IsPopupBlocked(Document* aDoc) {
674   if (aDoc->ConsumeTransientUserGestureActivation()) {
675     return false;
676   }
677 
678   WindowContext* wc = aDoc->GetWindowContext();
679   if (wc && wc->CanShowPopup()) {
680     return false;
681   }
682 
683   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, aDoc,
684                                   nsContentUtils::eDOM_PROPERTIES,
685                                   "InputPickerBlockedNoUserActivation");
686   return true;
687 }
688 
InitColorPicker()689 nsresult HTMLInputElement::InitColorPicker() {
690   if (mPickerRunning) {
691     NS_WARNING("Just one nsIColorPicker is allowed");
692     return NS_ERROR_FAILURE;
693   }
694 
695   nsCOMPtr<Document> doc = OwnerDoc();
696 
697   nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
698   if (!win) {
699     return NS_ERROR_FAILURE;
700   }
701 
702   if (IsPopupBlocked(doc)) {
703     return NS_OK;
704   }
705 
706   // Get Loc title
707   nsAutoString title;
708   nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
709                                      "ColorPicker", title);
710 
711   nsCOMPtr<nsIColorPicker> colorPicker =
712       do_CreateInstance("@mozilla.org/colorpicker;1");
713   if (!colorPicker) {
714     return NS_ERROR_FAILURE;
715   }
716 
717   nsAutoString initialValue;
718   GetNonFileValueInternal(initialValue);
719   nsresult rv = colorPicker->Init(win, title, initialValue);
720   NS_ENSURE_SUCCESS(rv, rv);
721 
722   nsCOMPtr<nsIColorPickerShownCallback> callback =
723       new nsColorPickerShownCallback(this, colorPicker);
724 
725   rv = colorPicker->Open(callback);
726   if (NS_SUCCEEDED(rv)) {
727     mPickerRunning = true;
728   }
729 
730   return rv;
731 }
732 
InitFilePicker(FilePickerType aType)733 nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
734   if (mPickerRunning) {
735     NS_WARNING("Just one nsIFilePicker is allowed");
736     return NS_ERROR_FAILURE;
737   }
738 
739   // Get parent nsPIDOMWindow object.
740   nsCOMPtr<Document> doc = OwnerDoc();
741 
742   nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
743   if (!win) {
744     return NS_ERROR_FAILURE;
745   }
746 
747   if (IsPopupBlocked(doc)) {
748     return NS_OK;
749   }
750 
751   // Get Loc title
752   nsAutoString title;
753   nsAutoString okButtonLabel;
754   if (aType == FILE_PICKER_DIRECTORY) {
755     nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
756                                             "DirectoryUpload", OwnerDoc(),
757                                             title);
758 
759     nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
760                                             "DirectoryPickerOkButtonLabel",
761                                             OwnerDoc(), okButtonLabel);
762   } else {
763     nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
764                                             "FileUpload", OwnerDoc(), title);
765   }
766 
767   nsCOMPtr<nsIFilePicker> filePicker =
768       do_CreateInstance("@mozilla.org/filepicker;1");
769   if (!filePicker) return NS_ERROR_FAILURE;
770 
771   int16_t mode;
772 
773   if (aType == FILE_PICKER_DIRECTORY) {
774     mode = static_cast<int16_t>(nsIFilePicker::modeGetFolder);
775   } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
776     mode = static_cast<int16_t>(nsIFilePicker::modeOpenMultiple);
777   } else {
778     mode = static_cast<int16_t>(nsIFilePicker::modeOpen);
779   }
780 
781   nsresult rv = filePicker->Init(win, title, mode);
782   NS_ENSURE_SUCCESS(rv, rv);
783 
784   if (!okButtonLabel.IsEmpty()) {
785     filePicker->SetOkButtonLabel(okButtonLabel);
786   }
787 
788   // Native directory pickers ignore file type filters, so we don't spend
789   // cycles adding them for FILE_PICKER_DIRECTORY.
790   if (HasAttr(kNameSpaceID_None, nsGkAtoms::accept) &&
791       aType != FILE_PICKER_DIRECTORY) {
792     SetFilePickerFiltersFromAccept(filePicker);
793 
794     if (StaticPrefs::dom_capture_enabled()) {
795       const nsAttrValue* captureVal =
796           GetParsedAttr(nsGkAtoms::capture, kNameSpaceID_None);
797       if (captureVal) {
798         filePicker->SetCapture(captureVal->GetEnumValue());
799       }
800     }
801   } else {
802     filePicker->AppendFilters(nsIFilePicker::filterAll);
803   }
804 
805   // Set default directory and filename
806   nsAutoString defaultName;
807 
808   const nsTArray<OwningFileOrDirectory>& oldFiles =
809       GetFilesOrDirectoriesInternal();
810 
811   nsCOMPtr<nsIFilePickerShownCallback> callback =
812       new HTMLInputElement::nsFilePickerShownCallback(this, filePicker);
813 
814   if (!oldFiles.IsEmpty() && aType != FILE_PICKER_DIRECTORY) {
815     nsAutoString path;
816 
817     nsCOMPtr<nsIFile> parentFile = LastUsedDirectory(oldFiles[0]);
818     if (parentFile) {
819       filePicker->SetDisplayDirectory(parentFile);
820     }
821 
822     // Unfortunately nsIFilePicker doesn't allow multiple files to be
823     // default-selected, so only select something by default if exactly
824     // one file was selected before.
825     if (oldFiles.Length() == 1) {
826       nsAutoString leafName;
827       GetDOMFileOrDirectoryName(oldFiles[0], leafName);
828 
829       if (!leafName.IsEmpty()) {
830         filePicker->SetDefaultString(leafName);
831       }
832     }
833 
834     rv = filePicker->Open(callback);
835     if (NS_SUCCEEDED(rv)) {
836       mPickerRunning = true;
837     }
838 
839     return rv;
840   }
841 
842   HTMLInputElement::gUploadLastDir->FetchDirectoryAndDisplayPicker(
843       doc, filePicker, callback);
844   mPickerRunning = true;
845   return NS_OK;
846 }
847 
848 #define CPS_PREF_NAME u"browser.upload.lastDir"_ns
849 
NS_IMPL_ISUPPORTS(UploadLastDir,nsIObserver,nsISupportsWeakReference)850 NS_IMPL_ISUPPORTS(UploadLastDir, nsIObserver, nsISupportsWeakReference)
851 
852 void HTMLInputElement::InitUploadLastDir() {
853   gUploadLastDir = new UploadLastDir();
854   NS_ADDREF(gUploadLastDir);
855 
856   nsCOMPtr<nsIObserverService> observerService =
857       mozilla::services::GetObserverService();
858   if (observerService && gUploadLastDir) {
859     observerService->AddObserver(gUploadLastDir,
860                                  "browser:purge-session-history", true);
861   }
862 }
863 
DestroyUploadLastDir()864 void HTMLInputElement::DestroyUploadLastDir() { NS_IF_RELEASE(gUploadLastDir); }
865 
FetchDirectoryAndDisplayPicker(Document * aDoc,nsIFilePicker * aFilePicker,nsIFilePickerShownCallback * aFpCallback)866 nsresult UploadLastDir::FetchDirectoryAndDisplayPicker(
867     Document* aDoc, nsIFilePicker* aFilePicker,
868     nsIFilePickerShownCallback* aFpCallback) {
869   MOZ_ASSERT(aDoc, "aDoc is null");
870   MOZ_ASSERT(aFilePicker, "aFilePicker is null");
871   MOZ_ASSERT(aFpCallback, "aFpCallback is null");
872 
873   nsIURI* docURI = aDoc->GetDocumentURI();
874   MOZ_ASSERT(docURI, "docURI is null");
875 
876   nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
877   nsCOMPtr<nsIContentPrefCallback2> prefCallback =
878       new UploadLastDir::ContentPrefCallback(aFilePicker, aFpCallback);
879 
880   // Attempt to get the CPS, if it's not present we'll fallback to use the
881   // Desktop folder
882   nsCOMPtr<nsIContentPrefService2> contentPrefService =
883       do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
884   if (!contentPrefService) {
885     prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
886     return NS_OK;
887   }
888 
889   nsAutoCString cstrSpec;
890   docURI->GetSpec(cstrSpec);
891   NS_ConvertUTF8toUTF16 spec(cstrSpec);
892 
893   contentPrefService->GetByDomainAndName(spec, CPS_PREF_NAME, loadContext,
894                                          prefCallback);
895   return NS_OK;
896 }
897 
StoreLastUsedDirectory(Document * aDoc,nsIFile * aDir)898 nsresult UploadLastDir::StoreLastUsedDirectory(Document* aDoc, nsIFile* aDir) {
899   MOZ_ASSERT(aDoc, "aDoc is null");
900   if (!aDir) {
901     return NS_OK;
902   }
903 
904   nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
905   MOZ_ASSERT(docURI, "docURI is null");
906 
907   // Attempt to get the CPS, if it's not present we'll just return
908   nsCOMPtr<nsIContentPrefService2> contentPrefService =
909       do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
910   if (!contentPrefService) return NS_ERROR_NOT_AVAILABLE;
911 
912   nsAutoCString cstrSpec;
913   docURI->GetSpec(cstrSpec);
914   NS_ConvertUTF8toUTF16 spec(cstrSpec);
915 
916   // Find the parent of aFile, and store it
917   nsString unicodePath;
918   aDir->GetPath(unicodePath);
919   if (unicodePath.IsEmpty())  // nothing to do
920     return NS_OK;
921   RefPtr<nsVariantCC> prefValue = new nsVariantCC();
922   prefValue->SetAsAString(unicodePath);
923 
924   // Use the document's current load context to ensure that the content pref
925   // service doesn't persistently store this directory for this domain if the
926   // user is using private browsing:
927   nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
928   return contentPrefService->Set(spec, CPS_PREF_NAME, prefValue, loadContext,
929                                  nullptr);
930 }
931 
932 NS_IMETHODIMP
Observe(nsISupports * aSubject,char const * aTopic,char16_t const * aData)933 UploadLastDir::Observe(nsISupports* aSubject, char const* aTopic,
934                        char16_t const* aData) {
935   if (strcmp(aTopic, "browser:purge-session-history") == 0) {
936     nsCOMPtr<nsIContentPrefService2> contentPrefService =
937         do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
938     if (contentPrefService)
939       contentPrefService->RemoveByName(CPS_PREF_NAME, nullptr, nullptr);
940   }
941   return NS_OK;
942 }
943 
944 #ifdef ACCESSIBILITY
945 // Helper method
946 static nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
947                                           EventMessage aEventMessage);
948 #endif
949 
950 //
951 // construction, destruction
952 //
953 
HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo,FromParser aFromParser,FromClone aFromClone)954 HTMLInputElement::HTMLInputElement(
955     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
956     FromParser aFromParser, FromClone aFromClone)
957     : TextControlElement(std::move(aNodeInfo), aFromParser,
958                          FormControlType(kInputDefaultType->value)),
959       mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown),
960       mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown),
961       mDisabledChanged(false),
962       mValueChanged(false),
963       mLastValueChangeWasInteractive(false),
964       mCheckedChanged(false),
965       mChecked(false),
966       mHandlingSelectEvent(false),
967       mShouldInitChecked(false),
968       mDoneCreating(aFromParser == NOT_FROM_PARSER &&
969                     aFromClone == FromClone::no),
970       mInInternalActivate(false),
971       mCheckedIsToggled(false),
972       mIndeterminate(false),
973       mInhibitRestoration(aFromParser & FROM_PARSER_FRAGMENT),
974       mCanShowValidUI(true),
975       mCanShowInvalidUI(true),
976       mHasRange(false),
977       mIsDraggingRange(false),
978       mNumberControlSpinnerIsSpinning(false),
979       mNumberControlSpinnerSpinsUp(false),
980       mPickerRunning(false),
981       mIsPreviewEnabled(false),
982       mHasBeenTypePassword(false),
983       mHasPatternAttribute(false) {
984   // If size is above 512, mozjemalloc allocates 1kB, see
985   // memory/build/mozjemalloc.cpp
986   static_assert(sizeof(HTMLInputElement) <= 512,
987                 "Keep the size of HTMLInputElement under 512 to avoid "
988                 "performance regression!");
989 
990   // We are in a type=text so we now we currenty need a TextControlState.
991   mInputData.mState = TextControlState::Construct(this);
992 
993   void* memory = mInputTypeMem;
994   mInputType = InputType::Create(this, mType, memory);
995 
996   if (!gUploadLastDir) HTMLInputElement::InitUploadLastDir();
997 
998   // Set up our default state.  By default we're enabled (since we're
999   // a control type that can be disabled but not actually disabled
1000   // right now), optional, and valid.  We are NOT readwrite by default
1001   // until someone calls UpdateEditableState on us, apparently!  Also
1002   // by default we don't have to show validity UI and so forth.
1003   AddStatesSilently(NS_EVENT_STATE_ENABLED | NS_EVENT_STATE_OPTIONAL |
1004                     NS_EVENT_STATE_VALID);
1005   UpdateApzAwareFlag();
1006 }
1007 
~HTMLInputElement()1008 HTMLInputElement::~HTMLInputElement() {
1009   if (mNumberControlSpinnerIsSpinning) {
1010     StopNumberControlSpinnerSpin(eDisallowDispatchingEvents);
1011   }
1012   nsImageLoadingContent::Destroy();
1013   FreeData();
1014 }
1015 
FreeData()1016 void HTMLInputElement::FreeData() {
1017   if (!IsSingleLineTextControl(false)) {
1018     free(mInputData.mValue);
1019     mInputData.mValue = nullptr;
1020   } else {
1021     UnbindFromFrame(nullptr);
1022     mInputData.mState->Destroy();
1023     mInputData.mState = nullptr;
1024   }
1025 
1026   if (mInputType) {
1027     mInputType->DropReference();
1028     mInputType = nullptr;
1029   }
1030 }
1031 
GetEditorState() const1032 TextControlState* HTMLInputElement::GetEditorState() const {
1033   if (!IsSingleLineTextControl(false)) {
1034     return nullptr;
1035   }
1036 
1037   MOZ_ASSERT(mInputData.mState,
1038              "Single line text controls need to have a state"
1039              " associated with them");
1040 
1041   return mInputData.mState;
1042 }
1043 
1044 // nsISupports
1045 
1046 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement)
1047 
1048 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
1049                                                   TextControlElement)
1050   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
1051   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
1052   if (tmp->IsSingleLineTextControl(false)) {
1053     tmp->mInputData.mState->Traverse(cb);
1054   }
1055 
1056   if (tmp->mFileData) {
1057     tmp->mFileData->Traverse(cb);
1058   }
1059 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1060 
1061 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
1062                                                 TextControlElement)
1063   NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
1064   NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
1065   if (tmp->IsSingleLineTextControl(false)) {
1066     tmp->mInputData.mState->Unlink();
1067   }
1068 
1069   if (tmp->mFileData) {
1070     tmp->mFileData->Unlink();
1071   }
1072   // XXX should unlink more?
1073 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1074 
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLInputElement,TextControlElement,imgINotificationObserver,nsIImageLoadingContent,nsIConstraintValidation)1075 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLInputElement,
1076                                              TextControlElement,
1077                                              imgINotificationObserver,
1078                                              nsIImageLoadingContent,
1079                                              nsIConstraintValidation)
1080 
1081 // nsINode
1082 
1083 nsresult HTMLInputElement::Clone(dom::NodeInfo* aNodeInfo,
1084                                  nsINode** aResult) const {
1085   *aResult = nullptr;
1086 
1087   RefPtr<HTMLInputElement> it = new (aNodeInfo->NodeInfoManager())
1088       HTMLInputElement(do_AddRef(aNodeInfo), NOT_FROM_PARSER, FromClone::yes);
1089 
1090   nsresult rv = const_cast<HTMLInputElement*>(this)->CopyInnerTo(it);
1091   NS_ENSURE_SUCCESS(rv, rv);
1092 
1093   switch (GetValueMode()) {
1094     case VALUE_MODE_VALUE:
1095       if (mValueChanged) {
1096         // We don't have our default value anymore.  Set our value on
1097         // the clone.
1098         nsAutoString value;
1099         GetNonFileValueInternal(value);
1100         // SetValueInternal handles setting the VALUE_CHANGED bit for us
1101         if (NS_WARN_IF(
1102                 NS_FAILED(rv = it->SetValueInternal(
1103                               value, {ValueSetterOption::SetValueChanged})))) {
1104           return rv;
1105         }
1106       }
1107       break;
1108     case VALUE_MODE_FILENAME:
1109       if (it->OwnerDoc()->IsStaticDocument()) {
1110         // We're going to be used in print preview.  Since the doc is static
1111         // we can just grab the pretty string and use it as wallpaper
1112         GetDisplayFileName(it->mFileData->mStaticDocFileList);
1113       } else {
1114         it->mFileData->ClearGetFilesHelpers();
1115         it->mFileData->mFilesOrDirectories.Clear();
1116         it->mFileData->mFilesOrDirectories.AppendElements(
1117             mFileData->mFilesOrDirectories);
1118       }
1119       break;
1120     case VALUE_MODE_DEFAULT_ON:
1121     case VALUE_MODE_DEFAULT:
1122       break;
1123   }
1124 
1125   if (mCheckedChanged) {
1126     // We no longer have our original checked state.  Set our
1127     // checked state on the clone.
1128     it->DoSetChecked(mChecked, false, true);
1129     // Then tell DoneCreatingElement() not to overwrite:
1130     it->mShouldInitChecked = false;
1131   }
1132 
1133   it->DoneCreatingElement();
1134 
1135   it->SetLastValueChangeWasInteractive(mLastValueChangeWasInteractive);
1136   it.forget(aResult);
1137   return NS_OK;
1138 }
1139 
BeforeSetAttr(int32_t aNameSpaceID,nsAtom * aName,const nsAttrValueOrString * aValue,bool aNotify)1140 nsresult HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1141                                          const nsAttrValueOrString* aValue,
1142                                          bool aNotify) {
1143   if (aNameSpaceID == kNameSpaceID_None) {
1144     if (aNotify && aName == nsGkAtoms::disabled) {
1145       mDisabledChanged = true;
1146     }
1147 
1148     // When name or type changes, radio should be removed from radio group.
1149     // If we are not done creating the radio, we also should not do it.
1150     if (mType == FormControlType::InputRadio) {
1151       if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
1152           (mForm || mDoneCreating)) {
1153         WillRemoveFromRadioGroup();
1154       } else if (aName == nsGkAtoms::required) {
1155         nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
1156 
1157         if (container && ((aValue && !HasAttr(aNameSpaceID, aName)) ||
1158                           (!aValue && HasAttr(aNameSpaceID, aName)))) {
1159           nsAutoString name;
1160           GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
1161           container->RadioRequiredWillChange(name, !!aValue);
1162         }
1163       }
1164     }
1165 
1166     if (aName == nsGkAtoms::webkitdirectory) {
1167       Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED, true);
1168     }
1169   }
1170 
1171   return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
1172                                                           aValue, aNotify);
1173 }
1174 
AfterSetAttr(int32_t aNameSpaceID,nsAtom * aName,const nsAttrValue * aValue,const nsAttrValue * aOldValue,nsIPrincipal * aSubjectPrincipal,bool aNotify)1175 nsresult HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1176                                         const nsAttrValue* aValue,
1177                                         const nsAttrValue* aOldValue,
1178                                         nsIPrincipal* aSubjectPrincipal,
1179                                         bool aNotify) {
1180   if (aNameSpaceID == kNameSpaceID_None) {
1181     if (aName == nsGkAtoms::src) {
1182       mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
1183           this, aValue ? aValue->GetStringValue() : EmptyString(),
1184           aSubjectPrincipal);
1185       if (aNotify && mType == FormControlType::InputImage) {
1186         if (aValue) {
1187           // Mark channel as urgent-start before load image if the image load is
1188           // initiated by a user interaction.
1189           mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
1190 
1191           LoadImage(aValue->GetStringValue(), true, aNotify,
1192                     eImageLoadType_Normal, mSrcTriggeringPrincipal);
1193         } else {
1194           // Null value means the attr got unset; drop the image
1195           CancelImageRequests(aNotify);
1196         }
1197       }
1198     }
1199 
1200     // If @value is changed and BF_VALUE_CHANGED is false, @value is the value
1201     // of the element so, if the value of the element is different than @value,
1202     // we have to re-set it. This is only the case when GetValueMode() returns
1203     // VALUE_MODE_VALUE.
1204     if (aName == nsGkAtoms::value) {
1205       if (!mValueChanged && GetValueMode() == VALUE_MODE_VALUE) {
1206         SetDefaultValueAsValue();
1207       }
1208       // GetStepBase() depends on the `value` attribute if `min` is not present,
1209       // even if the value doesn't change.
1210       UpdateStepMismatchValidityState();
1211     }
1212 
1213     //
1214     // Checked must be set no matter what type of control it is, since
1215     // mChecked must reflect the new value
1216     if (aName == nsGkAtoms::checked && !mCheckedChanged) {
1217       // Delay setting checked if we are creating this element (wait
1218       // until everything is set)
1219       if (!mDoneCreating) {
1220         mShouldInitChecked = true;
1221       } else {
1222         DoSetChecked(DefaultChecked(), true, false);
1223       }
1224     }
1225 
1226     if (aName == nsGkAtoms::type) {
1227       FormControlType newType;
1228       if (!aValue) {
1229         // We're now a text input.
1230         newType = FormControlType(kInputDefaultType->value);
1231       } else {
1232         newType = FormControlType(aValue->GetEnumValue());
1233       }
1234       if (newType != mType) {
1235         HandleTypeChange(newType, aNotify);
1236       }
1237     }
1238 
1239     // When name or type changes, radio should be added to radio group.
1240     // If we are not done creating the radio, we also should not do it.
1241     if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
1242         mType == FormControlType::InputRadio && (mForm || mDoneCreating)) {
1243       AddedToRadioGroup();
1244       UpdateValueMissingValidityStateForRadio(false);
1245     }
1246 
1247     if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
1248         aName == nsGkAtoms::readonly) {
1249       if (aName == nsGkAtoms::disabled) {
1250         // This *has* to be called *before* validity state check because
1251         // UpdateBarredFromConstraintValidation and
1252         // UpdateValueMissingValidityState depend on our disabled state.
1253         UpdateDisabledState(aNotify);
1254       }
1255 
1256       if (aName == nsGkAtoms::required && DoesRequiredApply()) {
1257         // This *has* to be called *before* UpdateValueMissingValidityState
1258         // because UpdateValueMissingValidityState depends on our required
1259         // state.
1260         UpdateRequiredState(!!aValue, aNotify);
1261       }
1262 
1263       UpdateValueMissingValidityState();
1264 
1265       // This *has* to be called *after* validity has changed.
1266       if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
1267         UpdateBarredFromConstraintValidation();
1268       }
1269     } else if (aName == nsGkAtoms::maxlength) {
1270       UpdateTooLongValidityState();
1271     } else if (aName == nsGkAtoms::minlength) {
1272       UpdateTooShortValidityState();
1273     } else if (aName == nsGkAtoms::pattern) {
1274       // Although pattern attribute only applies to single line text controls,
1275       // we set this flag for all input types to save having to check the type
1276       // here.
1277       mHasPatternAttribute = !!aValue;
1278 
1279       if (mDoneCreating) {
1280         UpdatePatternMismatchValidityState();
1281       }
1282     } else if (aName == nsGkAtoms::multiple) {
1283       UpdateTypeMismatchValidityState();
1284     } else if (aName == nsGkAtoms::max) {
1285       UpdateHasRange();
1286       nsresult rv = mInputType->MinMaxStepAttrChanged();
1287       NS_ENSURE_SUCCESS(rv, rv);
1288       // Validity state must be updated *after* the UpdateValueDueToAttrChange
1289       // call above or else the following assert will not be valid.
1290       // We don't assert the state of underflow during creation since
1291       // DoneCreatingElement sanitizes.
1292       UpdateRangeOverflowValidityState();
1293       MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1294                      !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1295                  "HTML5 spec does not allow underflow for type=range");
1296     } else if (aName == nsGkAtoms::min) {
1297       UpdateHasRange();
1298       nsresult rv = mInputType->MinMaxStepAttrChanged();
1299       NS_ENSURE_SUCCESS(rv, rv);
1300       // See corresponding @max comment
1301       UpdateRangeUnderflowValidityState();
1302       UpdateStepMismatchValidityState();
1303       MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1304                      !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1305                  "HTML5 spec does not allow underflow for type=range");
1306     } else if (aName == nsGkAtoms::step) {
1307       nsresult rv = mInputType->MinMaxStepAttrChanged();
1308       NS_ENSURE_SUCCESS(rv, rv);
1309       // See corresponding @max comment
1310       UpdateStepMismatchValidityState();
1311       MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1312                      !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1313                  "HTML5 spec does not allow underflow for type=range");
1314     } else if (aName == nsGkAtoms::dir && aValue &&
1315                aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
1316       SetDirectionFromValue(aNotify);
1317     } else if (aName == nsGkAtoms::lang) {
1318       // FIXME(emilio, bug 1651070): This doesn't account for lang changes on
1319       // ancestors.
1320       if (mType == FormControlType::InputNumber) {
1321         // The validity of our value may have changed based on the locale.
1322         UpdateValidityState();
1323       }
1324     } else if (aName == nsGkAtoms::autocomplete) {
1325       // Clear the cached @autocomplete attribute and autocompleteInfo state.
1326       mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
1327       mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
1328     } else if (aName == nsGkAtoms::placeholder) {
1329       // Full addition / removals of the attribute reconstruct right now.
1330       if (nsTextControlFrame* f = do_QueryFrame(GetPrimaryFrame())) {
1331         f->PlaceholderChanged(aOldValue, aValue);
1332       }
1333     }
1334 
1335     if (CreatesDateTimeWidget()) {
1336       if (aName == nsGkAtoms::value || aName == nsGkAtoms::readonly ||
1337           aName == nsGkAtoms::tabindex || aName == nsGkAtoms::required ||
1338           aName == nsGkAtoms::disabled) {
1339         // If original target is this and not the inner text control, we should
1340         // pass the focus to the inner text control.
1341         if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
1342           AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
1343               dateTimeBoxElement,
1344               aName == nsGkAtoms::value ? u"MozDateTimeValueChanged"_ns
1345                                         : u"MozDateTimeAttributeChanged"_ns,
1346               CanBubble::eNo, ChromeOnlyDispatch::eNo);
1347           dispatcher->RunDOMEventWhenSafe();
1348         }
1349       }
1350     }
1351   }
1352 
1353   return nsGenericHTMLFormElementWithState::AfterSetAttr(
1354       aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
1355 }
1356 
BeforeSetForm(bool aBindToTree)1357 void HTMLInputElement::BeforeSetForm(bool aBindToTree) {
1358   // No need to remove from radio group if we are just binding to tree.
1359   if (mType == FormControlType::InputRadio && !aBindToTree) {
1360     WillRemoveFromRadioGroup();
1361   }
1362 }
1363 
AfterClearForm(bool aUnbindOrDelete)1364 void HTMLInputElement::AfterClearForm(bool aUnbindOrDelete) {
1365   MOZ_ASSERT(!mForm);
1366 
1367   // Do not add back to radio group if we are releasing or unbinding from tree.
1368   if (mType == FormControlType::InputRadio && !aUnbindOrDelete) {
1369     AddedToRadioGroup();
1370     UpdateValueMissingValidityStateForRadio(false);
1371   }
1372 }
1373 
ResultForDialogSubmit(nsAString & aResult)1374 void HTMLInputElement::ResultForDialogSubmit(nsAString& aResult) {
1375   if (mType == FormControlType::InputImage) {
1376     // Get a property set by the frame to find out where it was clicked.
1377     nsIntPoint* lastClickedPoint =
1378         static_cast<nsIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
1379     int32_t x, y;
1380     if (lastClickedPoint) {
1381       x = lastClickedPoint->x;
1382       y = lastClickedPoint->y;
1383     } else {
1384       x = y = 0;
1385     }
1386     aResult.AppendInt(x);
1387     aResult.AppendLiteral(",");
1388     aResult.AppendInt(y);
1389   } else {
1390     GetAttr(kNameSpaceID_None, nsGkAtoms::value, aResult);
1391   }
1392 }
1393 
GetAutocomplete(nsAString & aValue)1394 void HTMLInputElement::GetAutocomplete(nsAString& aValue) {
1395   if (!DoesAutocompleteApply()) {
1396     return;
1397   }
1398 
1399   aValue.Truncate();
1400   const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1401 
1402   mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(
1403       attributeVal, aValue, mAutocompleteAttrState);
1404 }
1405 
GetAutocompleteInfo(Nullable<AutocompleteInfo> & aInfo)1406 void HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo) {
1407   if (!DoesAutocompleteApply()) {
1408     aInfo.SetNull();
1409     return;
1410   }
1411 
1412   const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1413   mAutocompleteInfoState = nsContentUtils::SerializeAutocompleteAttribute(
1414       attributeVal, aInfo.SetValue(), mAutocompleteInfoState, true);
1415 }
1416 
GetCapture(nsAString & aValue)1417 void HTMLInputElement::GetCapture(nsAString& aValue) {
1418   GetEnumAttr(nsGkAtoms::capture, kCaptureDefault->tag, aValue);
1419 }
1420 
GetFormEnctype(nsAString & aValue)1421 void HTMLInputElement::GetFormEnctype(nsAString& aValue) {
1422   GetEnumAttr(nsGkAtoms::formenctype, "", kFormDefaultEnctype->tag, aValue);
1423 }
1424 
GetFormMethod(nsAString & aValue)1425 void HTMLInputElement::GetFormMethod(nsAString& aValue) {
1426   GetEnumAttr(nsGkAtoms::formmethod, "", kFormDefaultMethod->tag, aValue);
1427 }
1428 
GetType(nsAString & aValue)1429 void HTMLInputElement::GetType(nsAString& aValue) {
1430   GetEnumAttr(nsGkAtoms::type, kInputDefaultType->tag, aValue);
1431 }
1432 
TabIndexDefault()1433 int32_t HTMLInputElement::TabIndexDefault() { return 0; }
1434 
Height()1435 uint32_t HTMLInputElement::Height() {
1436   if (mType != FormControlType::InputImage) {
1437     return 0;
1438   }
1439   return GetWidthHeightForImage().height;
1440 }
1441 
SetIndeterminateInternal(bool aValue,bool aShouldInvalidate)1442 void HTMLInputElement::SetIndeterminateInternal(bool aValue,
1443                                                 bool aShouldInvalidate) {
1444   mIndeterminate = aValue;
1445 
1446   if (aShouldInvalidate) {
1447     // Repaint the frame
1448     nsIFrame* frame = GetPrimaryFrame();
1449     if (frame) frame->InvalidateFrameSubtree();
1450   }
1451 
1452   UpdateState(true);
1453 }
1454 
SetIndeterminate(bool aValue)1455 void HTMLInputElement::SetIndeterminate(bool aValue) {
1456   SetIndeterminateInternal(aValue, true);
1457 }
1458 
Width()1459 uint32_t HTMLInputElement::Width() {
1460   if (mType != FormControlType::InputImage) {
1461     return 0;
1462   }
1463   return GetWidthHeightForImage().width;
1464 }
1465 
SanitizesOnValueGetter() const1466 bool HTMLInputElement::SanitizesOnValueGetter() const {
1467   // Don't return non-sanitized value for datetime types or number.
1468   return mType == FormControlType::InputNumber || IsDateTimeInputType(mType);
1469 }
1470 
GetValue(nsAString & aValue,CallerType aCallerType)1471 void HTMLInputElement::GetValue(nsAString& aValue, CallerType aCallerType) {
1472   GetValueInternal(aValue, aCallerType);
1473 
1474   if (SanitizesOnValueGetter()) {
1475     SanitizeValue(aValue, ForValueGetter::Yes);
1476   }
1477 }
1478 
GetValueInternal(nsAString & aValue,CallerType aCallerType) const1479 void HTMLInputElement::GetValueInternal(nsAString& aValue,
1480                                         CallerType aCallerType) const {
1481   if (mType != FormControlType::InputFile) {
1482     GetNonFileValueInternal(aValue);
1483     return;
1484   }
1485 
1486   if (aCallerType == CallerType::System) {
1487     aValue.Assign(mFileData->mFirstFilePath);
1488     return;
1489   }
1490 
1491   if (mFileData->mFilesOrDirectories.IsEmpty()) {
1492     aValue.Truncate();
1493     return;
1494   }
1495 
1496   nsAutoString file;
1497   GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], file);
1498   if (file.IsEmpty()) {
1499     aValue.Truncate();
1500     return;
1501   }
1502 
1503   aValue.AssignLiteral("C:\\fakepath\\");
1504   aValue.Append(file);
1505 }
1506 
GetNonFileValueInternal(nsAString & aValue) const1507 void HTMLInputElement::GetNonFileValueInternal(nsAString& aValue) const {
1508   switch (GetValueMode()) {
1509     case VALUE_MODE_VALUE:
1510       if (IsSingleLineTextControl(false)) {
1511         mInputData.mState->GetValue(aValue, true);
1512       } else if (!aValue.Assign(mInputData.mValue, fallible)) {
1513         aValue.Truncate();
1514       }
1515       return;
1516 
1517     case VALUE_MODE_FILENAME:
1518       MOZ_ASSERT_UNREACHABLE("Someone screwed up here");
1519       // We'll just return empty string if someone does screw up.
1520       aValue.Truncate();
1521       return;
1522 
1523     case VALUE_MODE_DEFAULT:
1524       // Treat defaultValue as value.
1525       GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue);
1526       return;
1527 
1528     case VALUE_MODE_DEFAULT_ON:
1529       // Treat default value as value and returns "on" if no value.
1530       if (!GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue)) {
1531         aValue.AssignLiteral("on");
1532       }
1533       return;
1534   }
1535 }
1536 
IsValueEmpty() const1537 bool HTMLInputElement::IsValueEmpty() const {
1538   if (GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false)) {
1539     return !mInputData.mState->HasNonEmptyValue();
1540   }
1541 
1542   nsAutoString value;
1543   GetNonFileValueInternal(value);
1544 
1545   return value.IsEmpty();
1546 }
1547 
ClearFiles(bool aSetValueChanged)1548 void HTMLInputElement::ClearFiles(bool aSetValueChanged) {
1549   nsTArray<OwningFileOrDirectory> data;
1550   SetFilesOrDirectories(data, aSetValueChanged);
1551 }
1552 
MonthsSinceJan1970(uint32_t aYear,uint32_t aMonth) const1553 int32_t HTMLInputElement::MonthsSinceJan1970(uint32_t aYear,
1554                                              uint32_t aMonth) const {
1555   return (aYear - 1970) * 12 + aMonth - 1;
1556 }
1557 
1558 /* static */
StringToDecimal(const nsAString & aValue)1559 Decimal HTMLInputElement::StringToDecimal(const nsAString& aValue) {
1560   if (!IsAscii(aValue)) {
1561     return Decimal::nan();
1562   }
1563   NS_LossyConvertUTF16toASCII asciiString(aValue);
1564   std::string stdString = asciiString.get();
1565   return Decimal::fromString(stdString);
1566 }
1567 
GetValueAsDecimal() const1568 Decimal HTMLInputElement::GetValueAsDecimal() const {
1569   Decimal decimalValue;
1570   nsAutoString stringValue;
1571 
1572   GetNonFileValueInternal(stringValue);
1573 
1574   return !mInputType->ConvertStringToNumber(stringValue, decimalValue)
1575              ? Decimal::nan()
1576              : decimalValue;
1577 }
1578 
SetValue(const nsAString & aValue,CallerType aCallerType,ErrorResult & aRv)1579 void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
1580                                 ErrorResult& aRv) {
1581   // check security.  Note that setting the value to the empty string is always
1582   // OK and gives pages a way to clear a file input if necessary.
1583   if (mType == FormControlType::InputFile) {
1584     if (!aValue.IsEmpty()) {
1585       if (aCallerType != CallerType::System) {
1586         // setting the value of a "FILE" input widget requires
1587         // chrome privilege
1588         aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1589         return;
1590       }
1591       Sequence<nsString> list;
1592       if (!list.AppendElement(aValue, fallible)) {
1593         aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1594         return;
1595       }
1596 
1597       MozSetFileNameArray(list, aRv);
1598       return;
1599     }
1600     ClearFiles(true);
1601   } else {
1602     if (MayFireChangeOnBlur()) {
1603       // If the value has been set by a script, we basically want to keep the
1604       // current change event state. If the element is ready to fire a change
1605       // event, we should keep it that way. Otherwise, we should make sure the
1606       // element will not fire any event because of the script interaction.
1607       //
1608       // NOTE: this is currently quite expensive work (too much string
1609       // manipulation). We should probably optimize that.
1610       nsAutoString currentValue;
1611       GetValue(currentValue, aCallerType);
1612 
1613       // Some types sanitize value, so GetValue doesn't return pure
1614       // previous value correctly.
1615       //
1616       // FIXME(emilio): Shouldn't above just use GetNonFileValueInternal() to
1617       // get the unsanitized value?
1618       nsresult rv = SetValueInternal(
1619           aValue, SanitizesOnValueGetter() ? nullptr : &currentValue,
1620           {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
1621            ValueSetterOption::MoveCursorToEndIfValueChanged});
1622       if (NS_FAILED(rv)) {
1623         aRv.Throw(rv);
1624         return;
1625       }
1626 
1627       if (mFocusedValue.Equals(currentValue)) {
1628         GetValue(mFocusedValue, aCallerType);
1629       }
1630     } else {
1631       nsresult rv = SetValueInternal(
1632           aValue,
1633           {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
1634            ValueSetterOption::MoveCursorToEndIfValueChanged});
1635       if (NS_FAILED(rv)) {
1636         aRv.Throw(rv);
1637         return;
1638       }
1639     }
1640   }
1641 }
1642 
GetList() const1643 nsGenericHTMLElement* HTMLInputElement::GetList() const {
1644   nsAutoString dataListId;
1645   GetAttr(kNameSpaceID_None, nsGkAtoms::list_, dataListId);
1646   if (dataListId.IsEmpty()) {
1647     return nullptr;
1648   }
1649 
1650   DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
1651   if (!docOrShadow) {
1652     return nullptr;
1653   }
1654 
1655   Element* element = docOrShadow->GetElementById(dataListId);
1656   if (!element || !element->IsHTMLElement(nsGkAtoms::datalist)) {
1657     return nullptr;
1658   }
1659 
1660   return static_cast<nsGenericHTMLElement*>(element);
1661 }
1662 
SetValue(Decimal aValue,CallerType aCallerType)1663 void HTMLInputElement::SetValue(Decimal aValue, CallerType aCallerType) {
1664   MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!");
1665 
1666   if (aValue.isNaN()) {
1667     SetValue(u""_ns, aCallerType, IgnoreErrors());
1668     return;
1669   }
1670 
1671   nsAutoString value;
1672   mInputType->ConvertNumberToString(aValue, value);
1673   SetValue(value, aCallerType, IgnoreErrors());
1674 }
1675 
GetValueAsDate(JSContext * aCx,JS::MutableHandle<JSObject * > aObject,ErrorResult & aRv)1676 void HTMLInputElement::GetValueAsDate(JSContext* aCx,
1677                                       JS::MutableHandle<JSObject*> aObject,
1678                                       ErrorResult& aRv) {
1679   aObject.set(nullptr);
1680   if (!IsDateTimeInputType(mType)) {
1681     return;
1682   }
1683 
1684   Maybe<JS::ClippedTime> time;
1685 
1686   switch (mType) {
1687     case FormControlType::InputDate: {
1688       uint32_t year, month, day;
1689       nsAutoString value;
1690       GetNonFileValueInternal(value);
1691       if (!ParseDate(value, &year, &month, &day)) {
1692         return;
1693       }
1694 
1695       time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day)));
1696       break;
1697     }
1698     case FormControlType::InputTime: {
1699       uint32_t millisecond;
1700       nsAutoString value;
1701       GetNonFileValueInternal(value);
1702       if (!ParseTime(value, &millisecond)) {
1703         return;
1704       }
1705 
1706       time.emplace(JS::TimeClip(millisecond));
1707       MOZ_ASSERT(time->toDouble() == millisecond,
1708                  "HTML times are restricted to the day after the epoch and "
1709                  "never clip");
1710       break;
1711     }
1712     case FormControlType::InputMonth: {
1713       uint32_t year, month;
1714       nsAutoString value;
1715       GetNonFileValueInternal(value);
1716       if (!ParseMonth(value, &year, &month)) {
1717         return;
1718       }
1719 
1720       time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, 1)));
1721       break;
1722     }
1723     case FormControlType::InputWeek: {
1724       uint32_t year, week;
1725       nsAutoString value;
1726       GetNonFileValueInternal(value);
1727       if (!ParseWeek(value, &year, &week)) {
1728         return;
1729       }
1730 
1731       double days = DaysSinceEpochFromWeek(year, week);
1732       time.emplace(JS::TimeClip(days * kMsPerDay));
1733 
1734       break;
1735     }
1736     case FormControlType::InputDatetimeLocal: {
1737       uint32_t year, month, day, timeInMs;
1738       nsAutoString value;
1739       GetNonFileValueInternal(value);
1740       if (!ParseDateTimeLocal(value, &year, &month, &day, &timeInMs)) {
1741         return;
1742       }
1743 
1744       time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day, timeInMs)));
1745       break;
1746     }
1747     default:
1748       break;
1749   }
1750 
1751   if (time) {
1752     aObject.set(JS::NewDateObject(aCx, *time));
1753     if (!aObject) {
1754       aRv.NoteJSContextException(aCx);
1755     }
1756     return;
1757   }
1758 
1759   MOZ_ASSERT(false, "Unrecognized input type");
1760   aRv.Throw(NS_ERROR_UNEXPECTED);
1761 }
1762 
SetValueAsDate(JSContext * aCx,JS::Handle<JSObject * > aObj,ErrorResult & aRv)1763 void HTMLInputElement::SetValueAsDate(JSContext* aCx,
1764                                       JS::Handle<JSObject*> aObj,
1765                                       ErrorResult& aRv) {
1766   if (!IsDateTimeInputType(mType)) {
1767     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1768     return;
1769   }
1770 
1771   if (aObj) {
1772     bool isDate;
1773     if (!JS::ObjectIsDate(aCx, aObj, &isDate)) {
1774       aRv.NoteJSContextException(aCx);
1775       return;
1776     }
1777     if (!isDate) {
1778       aRv.ThrowTypeError("Value being assigned is not a date.");
1779       return;
1780     }
1781   }
1782 
1783   double milliseconds;
1784   if (aObj) {
1785     if (!js::DateGetMsecSinceEpoch(aCx, aObj, &milliseconds)) {
1786       aRv.NoteJSContextException(aCx);
1787       return;
1788     }
1789   } else {
1790     milliseconds = UnspecifiedNaN<double>();
1791   }
1792 
1793   // At this point we know we're not a file input, so we can just pass "not
1794   // system" as the caller type, since the caller type only matters in the file
1795   // input case.
1796   if (IsNaN(milliseconds)) {
1797     SetValue(u""_ns, CallerType::NonSystem, aRv);
1798     return;
1799   }
1800 
1801   if (mType != FormControlType::InputMonth) {
1802     SetValue(Decimal::fromDouble(milliseconds), CallerType::NonSystem);
1803     return;
1804   }
1805 
1806   // type=month expects the value to be number of months.
1807   double year = JS::YearFromTime(milliseconds);
1808   double month = JS::MonthFromTime(milliseconds);
1809 
1810   if (IsNaN(year) || IsNaN(month)) {
1811     SetValue(u""_ns, CallerType::NonSystem, aRv);
1812     return;
1813   }
1814 
1815   int32_t months = MonthsSinceJan1970(year, month + 1);
1816   SetValue(Decimal(int32_t(months)), CallerType::NonSystem);
1817 }
1818 
SetValueAsNumber(double aValueAsNumber,ErrorResult & aRv)1819 void HTMLInputElement::SetValueAsNumber(double aValueAsNumber,
1820                                         ErrorResult& aRv) {
1821   // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
1822   // bug 825197.
1823   if (IsInfinite(aValueAsNumber)) {
1824     aRv.Throw(NS_ERROR_INVALID_ARG);
1825     return;
1826   }
1827 
1828   if (!DoesValueAsNumberApply()) {
1829     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1830     return;
1831   }
1832 
1833   // At this point we know we're not a file input, so we can just pass "not
1834   // system" as the caller type, since the caller type only matters in the file
1835   // input case.
1836   SetValue(Decimal::fromDouble(aValueAsNumber), CallerType::NonSystem);
1837 }
1838 
GetMinimum() const1839 Decimal HTMLInputElement::GetMinimum() const {
1840   MOZ_ASSERT(
1841       DoesValueAsNumberApply(),
1842       "GetMinimum() should only be used for types that allow .valueAsNumber");
1843 
1844   // Only type=range has a default minimum
1845   Decimal defaultMinimum =
1846       mType == FormControlType::InputRange ? Decimal(0) : Decimal::nan();
1847 
1848   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
1849     return defaultMinimum;
1850   }
1851 
1852   nsAutoString minStr;
1853   GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
1854 
1855   Decimal min;
1856   return mInputType->ConvertStringToNumber(minStr, min) ? min : defaultMinimum;
1857 }
1858 
GetMaximum() const1859 Decimal HTMLInputElement::GetMaximum() const {
1860   MOZ_ASSERT(
1861       DoesValueAsNumberApply(),
1862       "GetMaximum() should only be used for types that allow .valueAsNumber");
1863 
1864   // Only type=range has a default maximum
1865   Decimal defaultMaximum =
1866       mType == FormControlType::InputRange ? Decimal(100) : Decimal::nan();
1867 
1868   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
1869     return defaultMaximum;
1870   }
1871 
1872   nsAutoString maxStr;
1873   GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
1874 
1875   Decimal max;
1876   return mInputType->ConvertStringToNumber(maxStr, max) ? max : defaultMaximum;
1877 }
1878 
GetStepBase() const1879 Decimal HTMLInputElement::GetStepBase() const {
1880   MOZ_ASSERT(IsDateTimeInputType(mType) ||
1881                  mType == FormControlType::InputNumber ||
1882                  mType == FormControlType::InputRange,
1883              "Check that kDefaultStepBase is correct for this new type");
1884 
1885   Decimal stepBase;
1886 
1887   // Do NOT use GetMinimum here - the spec says to use "the min content
1888   // attribute", not "the minimum".
1889   nsAutoString minStr;
1890   if (GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr) &&
1891       mInputType->ConvertStringToNumber(minStr, stepBase)) {
1892     return stepBase;
1893   }
1894 
1895   // If @min is not a double, we should use @value.
1896   nsAutoString valueStr;
1897   if (GetAttr(kNameSpaceID_None, nsGkAtoms::value, valueStr) &&
1898       mInputType->ConvertStringToNumber(valueStr, stepBase)) {
1899     return stepBase;
1900   }
1901 
1902   if (mType == FormControlType::InputWeek) {
1903     return kDefaultStepBaseWeek;
1904   }
1905 
1906   return kDefaultStepBase;
1907 }
1908 
GetValueIfStepped(int32_t aStep,StepCallerType aCallerType,Decimal * aNextStep)1909 nsresult HTMLInputElement::GetValueIfStepped(int32_t aStep,
1910                                              StepCallerType aCallerType,
1911                                              Decimal* aNextStep) {
1912   if (!DoStepDownStepUpApply()) {
1913     return NS_ERROR_DOM_INVALID_STATE_ERR;
1914   }
1915 
1916   Decimal stepBase = GetStepBase();
1917   Decimal step = GetStep();
1918   if (step == kStepAny) {
1919     if (aCallerType != CALLED_FOR_USER_EVENT) {
1920       return NS_ERROR_DOM_INVALID_STATE_ERR;
1921     }
1922     // Allow the spin buttons and up/down arrow keys to do something sensible:
1923     step = GetDefaultStep();
1924   }
1925 
1926   Decimal minimum = GetMinimum();
1927   Decimal maximum = GetMaximum();
1928 
1929   if (!maximum.isNaN()) {
1930     // "max - (max - stepBase) % step" is the nearest valid value to max.
1931     maximum = maximum - NS_floorModulo(maximum - stepBase, step);
1932     if (!minimum.isNaN()) {
1933       if (minimum > maximum) {
1934         // Either the minimum was greater than the maximum prior to our
1935         // adjustment to align maximum on a step, or else (if we adjusted
1936         // maximum) there is no valid step between minimum and the unadjusted
1937         // maximum.
1938         return NS_OK;
1939       }
1940     }
1941   }
1942 
1943   Decimal value = GetValueAsDecimal();
1944   bool valueWasNaN = false;
1945   if (value.isNaN()) {
1946     value = Decimal(0);
1947     valueWasNaN = true;
1948   }
1949   Decimal valueBeforeStepping = value;
1950 
1951   Decimal deltaFromStep = NS_floorModulo(value - stepBase, step);
1952 
1953   if (deltaFromStep != Decimal(0)) {
1954     if (aStep > 0) {
1955       value += step - deltaFromStep;       // partial step
1956       value += step * Decimal(aStep - 1);  // then remaining steps
1957     } else if (aStep < 0) {
1958       value -= deltaFromStep;              // partial step
1959       value += step * Decimal(aStep + 1);  // then remaining steps
1960     }
1961   } else {
1962     value += step * Decimal(aStep);
1963   }
1964 
1965   if (value < minimum) {
1966     value = minimum;
1967     deltaFromStep = NS_floorModulo(value - stepBase, step);
1968     if (deltaFromStep != Decimal(0)) {
1969       value += step - deltaFromStep;
1970     }
1971   }
1972   if (value > maximum) {
1973     value = maximum;
1974     deltaFromStep = NS_floorModulo(value - stepBase, step);
1975     if (deltaFromStep != Decimal(0)) {
1976       value -= deltaFromStep;
1977     }
1978   }
1979 
1980   if (!valueWasNaN &&  // value="", resulting in us using "0"
1981       ((aStep > 0 && value < valueBeforeStepping) ||
1982        (aStep < 0 && value > valueBeforeStepping))) {
1983     // We don't want step-up to effectively step down, or step-down to
1984     // effectively step up, so return;
1985     return NS_OK;
1986   }
1987 
1988   *aNextStep = value;
1989   return NS_OK;
1990 }
1991 
ApplyStep(int32_t aStep)1992 nsresult HTMLInputElement::ApplyStep(int32_t aStep) {
1993   Decimal nextStep = Decimal::nan();  // unchanged if value will not change
1994 
1995   nsresult rv = GetValueIfStepped(aStep, CALLED_FOR_SCRIPT, &nextStep);
1996 
1997   if (NS_SUCCEEDED(rv) && nextStep.isFinite()) {
1998     // We know we're not a file input, so the caller type does not matter; just
1999     // pass "not system" to be safe.
2000     SetValue(nextStep, CallerType::NonSystem);
2001   }
2002 
2003   return rv;
2004 }
2005 
IsDateTimeInputType(FormControlType aType)2006 bool HTMLInputElement::IsDateTimeInputType(FormControlType aType) {
2007   switch (aType) {
2008     case FormControlType::InputDate:
2009     case FormControlType::InputTime:
2010     case FormControlType::InputMonth:
2011     case FormControlType::InputWeek:
2012     case FormControlType::InputDatetimeLocal:
2013       return true;
2014     default:
2015       return false;
2016   }
2017 }
2018 
MozGetFileNameArray(nsTArray<nsString> & aArray,ErrorResult & aRv)2019 void HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray,
2020                                            ErrorResult& aRv) {
2021   if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2022     return;
2023   }
2024 
2025   const nsTArray<OwningFileOrDirectory>& filesOrDirs =
2026       GetFilesOrDirectoriesInternal();
2027   for (uint32_t i = 0; i < filesOrDirs.Length(); i++) {
2028     nsAutoString str;
2029     GetDOMFileOrDirectoryPath(filesOrDirs[i], str, aRv);
2030     if (NS_WARN_IF(aRv.Failed())) {
2031       return;
2032     }
2033 
2034     aArray.AppendElement(str);
2035   }
2036 }
2037 
MozSetFileArray(const Sequence<OwningNonNull<File>> & aFiles)2038 void HTMLInputElement::MozSetFileArray(
2039     const Sequence<OwningNonNull<File>>& aFiles) {
2040   if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2041     return;
2042   }
2043 
2044   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2045   MOZ_ASSERT(global);
2046   if (!global) {
2047     return;
2048   }
2049 
2050   nsTArray<OwningFileOrDirectory> files;
2051   for (uint32_t i = 0; i < aFiles.Length(); ++i) {
2052     RefPtr<File> file = File::Create(global, aFiles[i].get()->Impl());
2053     if (NS_WARN_IF(!file)) {
2054       return;
2055     }
2056 
2057     OwningFileOrDirectory* element = files.AppendElement();
2058     element->SetAsFile() = file;
2059   }
2060 
2061   SetFilesOrDirectories(files, true);
2062 }
2063 
MozSetFileNameArray(const Sequence<nsString> & aFileNames,ErrorResult & aRv)2064 void HTMLInputElement::MozSetFileNameArray(const Sequence<nsString>& aFileNames,
2065                                            ErrorResult& aRv) {
2066   if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2067     return;
2068   }
2069 
2070   if (XRE_IsContentProcess()) {
2071     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
2072     return;
2073   }
2074 
2075   nsTArray<OwningFileOrDirectory> files;
2076   for (uint32_t i = 0; i < aFileNames.Length(); ++i) {
2077     nsCOMPtr<nsIFile> file;
2078 
2079     if (StringBeginsWith(aFileNames[i], u"file:"_ns,
2080                          nsASCIICaseInsensitiveStringComparator)) {
2081       // Converts the URL string into the corresponding nsIFile if possible
2082       // A local file will be created if the URL string begins with file://
2083       NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames[i]),
2084                             getter_AddRefs(file));
2085     }
2086 
2087     if (!file) {
2088       // this is no "file://", try as local file
2089       NS_NewLocalFile(aFileNames[i], false, getter_AddRefs(file));
2090     }
2091 
2092     if (!file) {
2093       continue;  // Not much we can do if the file doesn't exist
2094     }
2095 
2096     nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2097     if (!global) {
2098       aRv.Throw(NS_ERROR_FAILURE);
2099       return;
2100     }
2101 
2102     RefPtr<File> domFile = File::CreateFromFile(global, file);
2103     if (NS_WARN_IF(!domFile)) {
2104       aRv.Throw(NS_ERROR_FAILURE);
2105       return;
2106     }
2107 
2108     OwningFileOrDirectory* element = files.AppendElement();
2109     element->SetAsFile() = domFile;
2110   }
2111 
2112   SetFilesOrDirectories(files, true);
2113 }
2114 
MozSetDirectory(const nsAString & aDirectoryPath,ErrorResult & aRv)2115 void HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
2116                                        ErrorResult& aRv) {
2117   if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2118     return;
2119   }
2120 
2121   nsCOMPtr<nsIFile> file;
2122   aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));
2123   if (NS_WARN_IF(aRv.Failed())) {
2124     return;
2125   }
2126 
2127   nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
2128   if (NS_WARN_IF(!window)) {
2129     aRv.Throw(NS_ERROR_FAILURE);
2130     return;
2131   }
2132 
2133   RefPtr<Directory> directory = Directory::Create(window->AsGlobal(), file);
2134   MOZ_ASSERT(directory);
2135 
2136   nsTArray<OwningFileOrDirectory> array;
2137   OwningFileOrDirectory* element = array.AppendElement();
2138   element->SetAsDirectory() = directory;
2139 
2140   SetFilesOrDirectories(array, true);
2141 }
2142 
GetDateTimeInputBoxValue(DateTimeValue & aValue)2143 void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue) {
2144   if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
2145     return;
2146   }
2147 
2148   aValue = *mDateTimeInputBoxValue;
2149 }
2150 
GetDateTimeBoxElement()2151 Element* HTMLInputElement::GetDateTimeBoxElement() {
2152   if (!GetShadowRoot()) {
2153     return nullptr;
2154   }
2155 
2156   // The datetimebox <div> is the only child of the UA Widget Shadow Root
2157   // if it is present.
2158   MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
2159   MOZ_ASSERT(1 >= GetShadowRoot()->GetChildCount());
2160   if (nsIContent* inputAreaContent = GetShadowRoot()->GetFirstChild()) {
2161     return inputAreaContent->AsElement();
2162   }
2163 
2164   return nullptr;
2165 }
2166 
OpenDateTimePicker(const DateTimeValue & aInitialValue)2167 void HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue) {
2168   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2169     return;
2170   }
2171 
2172   mDateTimeInputBoxValue = MakeUnique<DateTimeValue>(aInitialValue);
2173   nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2174                                       u"MozOpenDateTimePicker"_ns,
2175                                       CanBubble::eYes, Cancelable::eYes);
2176 }
2177 
UpdateDateTimePicker(const DateTimeValue & aValue)2178 void HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue) {
2179   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2180     return;
2181   }
2182 
2183   mDateTimeInputBoxValue = MakeUnique<DateTimeValue>(aValue);
2184   nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2185                                       u"MozUpdateDateTimePicker"_ns,
2186                                       CanBubble::eYes, Cancelable::eYes);
2187 }
2188 
CloseDateTimePicker()2189 void HTMLInputElement::CloseDateTimePicker() {
2190   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2191     return;
2192   }
2193 
2194   nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2195                                       u"MozCloseDateTimePicker"_ns,
2196                                       CanBubble::eYes, Cancelable::eYes);
2197 }
2198 
SetFocusState(bool aIsFocused)2199 void HTMLInputElement::SetFocusState(bool aIsFocused) {
2200   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2201     return;
2202   }
2203 
2204   EventStates focusStates = NS_EVENT_STATE_FOCUS | NS_EVENT_STATE_FOCUSRING;
2205   if (aIsFocused) {
2206     AddStates(focusStates);
2207   } else {
2208     RemoveStates(focusStates);
2209   }
2210 }
2211 
UpdateValidityState()2212 void HTMLInputElement::UpdateValidityState() {
2213   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2214     return;
2215   }
2216 
2217   // For now, datetime input box call this function only when the value may
2218   // become valid/invalid. For other validity states, they will be updated when
2219   // .value is actually changed.
2220   UpdateBadInputValidityState();
2221   UpdateState(true);
2222 }
2223 
MozIsTextField(bool aExcludePassword)2224 bool HTMLInputElement::MozIsTextField(bool aExcludePassword) {
2225   // TODO: temporary until bug 888320 is fixed.
2226   //
2227   // FIXME: Historically we never returned true for `number`, we should consider
2228   // changing that now that it is similar to other inputs.
2229   if (IsDateTimeInputType(mType) || mType == FormControlType::InputNumber) {
2230     return false;
2231   }
2232 
2233   return IsSingleLineTextControl(aExcludePassword);
2234 }
2235 
SetUserInput(const nsAString & aValue,nsIPrincipal & aSubjectPrincipal)2236 void HTMLInputElement::SetUserInput(const nsAString& aValue,
2237                                     nsIPrincipal& aSubjectPrincipal) {
2238   AutoHandlingUserInputStatePusher inputStatePusher(true);
2239 
2240   if (mType == FormControlType::InputFile &&
2241       !aSubjectPrincipal.IsSystemPrincipal()) {
2242     return;
2243   }
2244 
2245   if (mType == FormControlType::InputFile) {
2246     Sequence<nsString> list;
2247     if (!list.AppendElement(aValue, fallible)) {
2248       return;
2249     }
2250 
2251     MozSetFileNameArray(list, IgnoreErrors());
2252     return;
2253   }
2254 
2255   bool isInputEventDispatchedByTextControlState =
2256       GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false);
2257 
2258   nsresult rv = SetValueInternal(
2259       aValue,
2260       {ValueSetterOption::BySetUserInputAPI, ValueSetterOption::SetValueChanged,
2261        ValueSetterOption::MoveCursorToEndIfValueChanged});
2262   NS_ENSURE_SUCCESS_VOID(rv);
2263 
2264   if (!isInputEventDispatchedByTextControlState) {
2265     DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
2266     NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2267                          "Failed to dispatch input event");
2268   }
2269 
2270   // If this element is not currently focused, it won't receive a change event
2271   // for this update through the normal channels. So fire a change event
2272   // immediately, instead.
2273   if (!ShouldBlur(this)) {
2274     FireChangeEventIfNeeded();
2275   }
2276 }
2277 
GetEditorForBindings()2278 nsIEditor* HTMLInputElement::GetEditorForBindings() {
2279   if (!GetPrimaryFrame()) {
2280     // Ensure we construct frames (and thus an editor) if needed.
2281     GetPrimaryFrame(FlushType::Frames);
2282   }
2283   return GetTextEditorFromState();
2284 }
2285 
HasEditor()2286 bool HTMLInputElement::HasEditor() { return !!GetTextEditorWithoutCreation(); }
2287 
GetTextEditorFromState()2288 TextEditor* HTMLInputElement::GetTextEditorFromState() {
2289   TextControlState* state = GetEditorState();
2290   if (state) {
2291     return state->GetTextEditor();
2292   }
2293   return nullptr;
2294 }
2295 
GetTextEditor()2296 TextEditor* HTMLInputElement::GetTextEditor() {
2297   return GetTextEditorFromState();
2298 }
2299 
GetTextEditorWithoutCreation()2300 TextEditor* HTMLInputElement::GetTextEditorWithoutCreation() {
2301   TextControlState* state = GetEditorState();
2302   if (!state) {
2303     return nullptr;
2304   }
2305   return state->GetTextEditorWithoutCreation();
2306 }
2307 
GetSelectionController()2308 nsISelectionController* HTMLInputElement::GetSelectionController() {
2309   TextControlState* state = GetEditorState();
2310   if (state) {
2311     return state->GetSelectionController();
2312   }
2313   return nullptr;
2314 }
2315 
GetConstFrameSelection()2316 nsFrameSelection* HTMLInputElement::GetConstFrameSelection() {
2317   TextControlState* state = GetEditorState();
2318   if (state) {
2319     return state->GetConstFrameSelection();
2320   }
2321   return nullptr;
2322 }
2323 
BindToFrame(nsTextControlFrame * aFrame)2324 nsresult HTMLInputElement::BindToFrame(nsTextControlFrame* aFrame) {
2325   MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
2326   TextControlState* state = GetEditorState();
2327   if (state) {
2328     return state->BindToFrame(aFrame);
2329   }
2330   return NS_ERROR_FAILURE;
2331 }
2332 
UnbindFromFrame(nsTextControlFrame * aFrame)2333 void HTMLInputElement::UnbindFromFrame(nsTextControlFrame* aFrame) {
2334   TextControlState* state = GetEditorState();
2335   if (state && aFrame) {
2336     state->UnbindFromFrame(aFrame);
2337   }
2338 }
2339 
CreateEditor()2340 nsresult HTMLInputElement::CreateEditor() {
2341   TextControlState* state = GetEditorState();
2342   if (state) {
2343     return state->PrepareEditor();
2344   }
2345   return NS_ERROR_FAILURE;
2346 }
2347 
SetPreviewValue(const nsAString & aValue)2348 void HTMLInputElement::SetPreviewValue(const nsAString& aValue) {
2349   TextControlState* state = GetEditorState();
2350   if (state) {
2351     state->SetPreviewText(aValue, true);
2352   }
2353 }
2354 
GetPreviewValue(nsAString & aValue)2355 void HTMLInputElement::GetPreviewValue(nsAString& aValue) {
2356   TextControlState* state = GetEditorState();
2357   if (state) {
2358     state->GetPreviewText(aValue);
2359   }
2360 }
2361 
EnablePreview()2362 void HTMLInputElement::EnablePreview() {
2363   if (mIsPreviewEnabled) {
2364     return;
2365   }
2366 
2367   mIsPreviewEnabled = true;
2368   // Reconstruct the frame to append an anonymous preview node
2369   nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0},
2370                                   nsChangeHint_ReconstructFrame);
2371 }
2372 
IsPreviewEnabled()2373 bool HTMLInputElement::IsPreviewEnabled() { return mIsPreviewEnabled; }
2374 
GetDisplayFileName(nsAString & aValue) const2375 void HTMLInputElement::GetDisplayFileName(nsAString& aValue) const {
2376   MOZ_ASSERT(mFileData);
2377 
2378   if (OwnerDoc()->IsStaticDocument()) {
2379     aValue = mFileData->mStaticDocFileList;
2380     return;
2381   }
2382 
2383   if (mFileData->mFilesOrDirectories.Length() == 1) {
2384     GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], aValue);
2385     return;
2386   }
2387 
2388   nsAutoString value;
2389 
2390   if (mFileData->mFilesOrDirectories.IsEmpty()) {
2391     if ((StaticPrefs::dom_input_dirpicker() && Allowdirs()) ||
2392         (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2393          HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
2394       nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2395                                               "NoDirSelected", OwnerDoc(),
2396                                               value);
2397     } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
2398       nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2399                                               "NoFilesSelected", OwnerDoc(),
2400                                               value);
2401     } else {
2402       nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2403                                               "NoFileSelected", OwnerDoc(),
2404                                               value);
2405     }
2406   } else {
2407     nsString count;
2408     count.AppendInt(int(mFileData->mFilesOrDirectories.Length()));
2409 
2410     nsContentUtils::FormatMaybeLocalizedString(
2411         value, nsContentUtils::eFORMS_PROPERTIES, "XFilesSelected", OwnerDoc(),
2412         count);
2413   }
2414 
2415   aValue = value;
2416 }
2417 
2418 const nsTArray<OwningFileOrDirectory>&
GetFilesOrDirectoriesInternal() const2419 HTMLInputElement::GetFilesOrDirectoriesInternal() const {
2420   return mFileData->mFilesOrDirectories;
2421 }
2422 
SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories,bool aSetValueChanged)2423 void HTMLInputElement::SetFilesOrDirectories(
2424     const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
2425     bool aSetValueChanged) {
2426   if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2427     return;
2428   }
2429 
2430   MOZ_ASSERT(mFileData);
2431 
2432   mFileData->ClearGetFilesHelpers();
2433 
2434   if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2435     HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2436     mFileData->mEntries.Clear();
2437   }
2438 
2439   mFileData->mFilesOrDirectories.Clear();
2440   mFileData->mFilesOrDirectories.AppendElements(aFilesOrDirectories);
2441 
2442   AfterSetFilesOrDirectories(aSetValueChanged);
2443 }
2444 
SetFiles(FileList * aFiles,bool aSetValueChanged)2445 void HTMLInputElement::SetFiles(FileList* aFiles, bool aSetValueChanged) {
2446   MOZ_ASSERT(mFileData);
2447 
2448   mFileData->mFilesOrDirectories.Clear();
2449   mFileData->ClearGetFilesHelpers();
2450 
2451   if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2452     HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2453     mFileData->mEntries.Clear();
2454   }
2455 
2456   if (aFiles) {
2457     uint32_t listLength = aFiles->Length();
2458     for (uint32_t i = 0; i < listLength; i++) {
2459       OwningFileOrDirectory* element =
2460           mFileData->mFilesOrDirectories.AppendElement();
2461       element->SetAsFile() = aFiles->Item(i);
2462     }
2463   }
2464 
2465   AfterSetFilesOrDirectories(aSetValueChanged);
2466 }
2467 
2468 // This method is used for testing only.
MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories)2469 void HTMLInputElement::MozSetDndFilesAndDirectories(
2470     const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories) {
2471   if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2472     return;
2473   }
2474 
2475   SetFilesOrDirectories(aFilesOrDirectories, true);
2476 
2477   if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2478     UpdateEntries(aFilesOrDirectories);
2479   }
2480 
2481   RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
2482       new DispatchChangeEventCallback(this);
2483 
2484   if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2485       HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
2486     ErrorResult rv;
2487     GetFilesHelper* helper =
2488         GetOrCreateGetFilesHelper(true /* recursionFlag */, rv);
2489     if (NS_WARN_IF(rv.Failed())) {
2490       rv.SuppressException();
2491       return;
2492     }
2493 
2494     helper->AddCallback(dispatchChangeEventCallback);
2495   } else {
2496     dispatchChangeEventCallback->DispatchEvents();
2497   }
2498 }
2499 
AfterSetFilesOrDirectories(bool aSetValueChanged)2500 void HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged) {
2501   // No need to flush here, if there's no frame at this point we
2502   // don't need to force creation of one just to tell it about this
2503   // new value.  We just want the display to update as needed.
2504   nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
2505   if (formControlFrame) {
2506     nsAutoString readableValue;
2507     GetDisplayFileName(readableValue);
2508     formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue);
2509   }
2510 
2511   // Grab the full path here for any chrome callers who access our .value via a
2512   // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
2513   // call under GetMozFullPath won't be rejected for not being urgent.
2514   // XXX Protected by the ifndef because the blob code doesn't allow us to send
2515   // this message in b2g.
2516   if (mFileData->mFilesOrDirectories.IsEmpty()) {
2517     mFileData->mFirstFilePath.Truncate();
2518   } else {
2519     ErrorResult rv;
2520     GetDOMFileOrDirectoryPath(mFileData->mFilesOrDirectories[0],
2521                               mFileData->mFirstFilePath, rv);
2522     if (NS_WARN_IF(rv.Failed())) {
2523       rv.SuppressException();
2524     }
2525   }
2526 
2527   UpdateFileList();
2528 
2529   if (aSetValueChanged) {
2530     SetValueChanged(true);
2531   }
2532 
2533   UpdateAllValidityStates(true);
2534 }
2535 
FireChangeEventIfNeeded()2536 void HTMLInputElement::FireChangeEventIfNeeded() {
2537   // We're not exposing the GetValue return value anywhere here, so it's safe to
2538   // claim to be a system caller.
2539   nsAutoString value;
2540   GetValue(value, CallerType::System);
2541 
2542   if (!MayFireChangeOnBlur() || mFocusedValue.Equals(value)) {
2543     return;
2544   }
2545 
2546   // Dispatch the change event.
2547   mFocusedValue = value;
2548   nsContentUtils::DispatchTrustedEvent(
2549       OwnerDoc(), static_cast<nsIContent*>(this), u"change"_ns, CanBubble::eYes,
2550       Cancelable::eNo);
2551 }
2552 
GetFiles()2553 FileList* HTMLInputElement::GetFiles() {
2554   if (mType != FormControlType::InputFile) {
2555     return nullptr;
2556   }
2557 
2558   if (StaticPrefs::dom_input_dirpicker() && Allowdirs() &&
2559       (!StaticPrefs::dom_webkitBlink_dirPicker_enabled() ||
2560        !HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
2561     return nullptr;
2562   }
2563 
2564   if (!mFileData->mFileList) {
2565     mFileData->mFileList = new FileList(static_cast<nsIContent*>(this));
2566     UpdateFileList();
2567   }
2568 
2569   return mFileData->mFileList;
2570 }
2571 
SetFiles(FileList * aFiles)2572 void HTMLInputElement::SetFiles(FileList* aFiles) {
2573   if (mType != FormControlType::InputFile || !aFiles) {
2574     return;
2575   }
2576 
2577   // Clear |mFileData->mFileList| to omit |UpdateFileList|
2578   if (mFileData->mFileList) {
2579     mFileData->mFileList->Clear();
2580     mFileData->mFileList = nullptr;
2581   }
2582 
2583   // Update |mFileData->mFilesOrDirectories|
2584   SetFiles(aFiles, true);
2585 
2586   // Update |mFileData->mFileList| without copy
2587   mFileData->mFileList = aFiles;
2588 }
2589 
2590 /* static */
HandleNumberControlSpin(void * aData)2591 void HTMLInputElement::HandleNumberControlSpin(void* aData) {
2592   RefPtr<HTMLInputElement> input = static_cast<HTMLInputElement*>(aData);
2593 
2594   NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
2595                "Should have called nsRepeatService::Stop()");
2596 
2597   nsNumberControlFrame* numberControlFrame =
2598       do_QueryFrame(input->GetPrimaryFrame());
2599   if (input->mType != FormControlType::InputNumber || !numberControlFrame) {
2600     // Type has changed (and possibly our frame type hasn't been updated yet)
2601     // or else we've lost our frame. Either way, stop the timer and don't do
2602     // anything else.
2603     input->StopNumberControlSpinnerSpin();
2604   } else {
2605     input->StepNumberControlForUserEvent(
2606         input->mNumberControlSpinnerSpinsUp ? 1 : -1);
2607   }
2608 }
2609 
UpdateFileList()2610 void HTMLInputElement::UpdateFileList() {
2611   MOZ_ASSERT(mFileData);
2612 
2613   if (mFileData->mFileList) {
2614     mFileData->mFileList->Clear();
2615 
2616     const nsTArray<OwningFileOrDirectory>& array =
2617         GetFilesOrDirectoriesInternal();
2618 
2619     for (uint32_t i = 0; i < array.Length(); ++i) {
2620       if (array[i].IsFile()) {
2621         mFileData->mFileList->Append(array[i].GetAsFile());
2622       }
2623     }
2624   }
2625 }
2626 
SetValueInternal(const nsAString & aValue,const nsAString * aOldValue,const ValueSetterOptions & aOptions)2627 nsresult HTMLInputElement::SetValueInternal(
2628     const nsAString& aValue, const nsAString* aOldValue,
2629     const ValueSetterOptions& aOptions) {
2630   MOZ_ASSERT(GetValueMode() != VALUE_MODE_FILENAME,
2631              "Don't call SetValueInternal for file inputs");
2632 
2633   // We want to remember if the SetValueInternal() call is being made for a XUL
2634   // element.  We do that by looking at the parent node here, and if that node
2635   // is a XUL node, we consider our control a XUL control. XUL controls preserve
2636   // edit history across value setters.
2637   //
2638   // TODO(emilio): Rather than doing this maybe add an attribute instead and
2639   // read it only on chrome docs or something? That'd allow front-end code to
2640   // move away from xul without weird side-effects.
2641   const bool forcePreserveUndoHistory = mParent && mParent->IsXULElement();
2642 
2643   switch (GetValueMode()) {
2644     case VALUE_MODE_VALUE: {
2645       // At the moment, only single line text control have to sanitize their
2646       // value Because we have to create a new string for that, we should
2647       // prevent doing it if it's useless.
2648       nsAutoString value(aValue);
2649 
2650       if (mDoneCreating) {
2651         SanitizeValue(value);
2652       }
2653       // else DoneCreatingElement calls us again once mDoneCreating is true
2654 
2655       const bool setValueChanged =
2656           aOptions.contains(ValueSetterOption::SetValueChanged);
2657       if (setValueChanged) {
2658         SetValueChanged(true);
2659       }
2660 
2661       if (IsSingleLineTextControl(false)) {
2662         // Note that if aOptions includes
2663         // ValueSetterOption::BySetUserInputAPI, "input" event is automatically
2664         // dispatched by TextControlState::SetValue(). If you'd change condition
2665         // of calling this method, you need to maintain SetUserInput() too. FYI:
2666         // After calling SetValue(), the input type might have been
2667         //      modified so that mInputData may not store TextControlState.
2668         if (!mInputData.mState->SetValue(
2669                 value, aOldValue,
2670                 forcePreserveUndoHistory
2671                     ? aOptions + ValueSetterOption::PreserveUndoHistory
2672                     : aOptions)) {
2673           return NS_ERROR_OUT_OF_MEMORY;
2674         }
2675         // If the caller won't dispatch "input" event via
2676         // nsContentUtils::DispatchInputEvent(), we need to modify
2677         // validationMessage value here.
2678         //
2679         // FIXME(emilio): ValueSetterOption::ByInternalAPI is not supposed to
2680         // change state, but maybe we could run this too?
2681         if (aOptions.contains(ValueSetterOption::ByContentAPI)) {
2682           MaybeUpdateAllValidityStates(!mDoneCreating);
2683         }
2684       } else {
2685         free(mInputData.mValue);
2686         mInputData.mValue = ToNewUnicode(value);
2687         if (setValueChanged) {
2688           SetValueChanged(true);
2689         }
2690         if (mType == FormControlType::InputRange) {
2691           nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
2692           if (frame) {
2693             frame->UpdateForValueChange();
2694           }
2695         } else if (CreatesDateTimeWidget() &&
2696                    !aOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
2697           if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
2698             AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
2699                 dateTimeBoxElement, u"MozDateTimeValueChanged"_ns,
2700                 CanBubble::eNo, ChromeOnlyDispatch::eNo);
2701             dispatcher->RunDOMEventWhenSafe();
2702           }
2703         }
2704         if (mDoneCreating) {
2705           OnValueChanged(ValueChangeKind::Internal);
2706         }
2707         // else DoneCreatingElement calls us again once mDoneCreating is true
2708       }
2709 
2710       if (mType == FormControlType::InputColor) {
2711         // Update color frame, to reflect color changes
2712         nsColorControlFrame* colorControlFrame =
2713             do_QueryFrame(GetPrimaryFrame());
2714         if (colorControlFrame) {
2715           colorControlFrame->UpdateColor();
2716         }
2717       }
2718 
2719       // This call might be useless in some situations because if the element is
2720       // a single line text control, TextControlState::SetValue will call
2721       // nsHTMLInputElement::OnValueChanged which is going to call UpdateState()
2722       // if the element is focused. This bug 665547.
2723       if (PlaceholderApplies() && HasAttr(nsGkAtoms::placeholder)) {
2724         UpdateState(true);
2725       }
2726 
2727       return NS_OK;
2728     }
2729 
2730     case VALUE_MODE_DEFAULT:
2731     case VALUE_MODE_DEFAULT_ON:
2732       // If the value of a hidden input was changed, we mark it changed so that
2733       // we will know we need to save / restore the value.  Yes, we are
2734       // overloading the meaning of ValueChanged just a teensy bit to save a
2735       // measly byte of storage space in HTMLInputElement.  Yes, you are free to
2736       // make a new flag, NEED_TO_SAVE_VALUE, at such time as mBitField becomes
2737       // a 16-bit value.
2738       if (mType == FormControlType::InputHidden) {
2739         SetValueChanged(true);
2740       }
2741 
2742       // Treat value == defaultValue for other input elements.
2743       return nsGenericHTMLFormElementWithState::SetAttr(
2744           kNameSpaceID_None, nsGkAtoms::value, aValue, true);
2745 
2746     case VALUE_MODE_FILENAME:
2747       return NS_ERROR_UNEXPECTED;
2748   }
2749 
2750   // This return statement is required for some compilers.
2751   return NS_OK;
2752 }
2753 
SetValueChanged(bool aValueChanged)2754 nsresult HTMLInputElement::SetValueChanged(bool aValueChanged) {
2755   if (mValueChanged == aValueChanged) {
2756     return NS_OK;
2757   }
2758   mValueChanged = aValueChanged;
2759   UpdateTooLongValidityState();
2760   UpdateTooShortValidityState();
2761   // We need to do this unconditionally because the validity ui bits depend on
2762   // this.
2763   UpdateState(true);
2764   return NS_OK;
2765 }
2766 
SetLastValueChangeWasInteractive(bool aWasInteractive)2767 void HTMLInputElement::SetLastValueChangeWasInteractive(bool aWasInteractive) {
2768   if (aWasInteractive == mLastValueChangeWasInteractive) {
2769     return;
2770   }
2771   mLastValueChangeWasInteractive = aWasInteractive;
2772   const bool wasValid = IsValid();
2773   UpdateTooLongValidityState();
2774   UpdateTooShortValidityState();
2775   if (wasValid != IsValid()) {
2776     UpdateState(true);
2777   }
2778 }
2779 
SetCheckedChanged(bool aCheckedChanged)2780 void HTMLInputElement::SetCheckedChanged(bool aCheckedChanged) {
2781   DoSetCheckedChanged(aCheckedChanged, true);
2782 }
2783 
DoSetCheckedChanged(bool aCheckedChanged,bool aNotify)2784 void HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged, bool aNotify) {
2785   if (mType == FormControlType::InputRadio) {
2786     if (mCheckedChanged != aCheckedChanged) {
2787       nsCOMPtr<nsIRadioVisitor> visitor =
2788           new nsRadioSetCheckedChangedVisitor(aCheckedChanged);
2789       VisitGroup(visitor);
2790     }
2791   } else {
2792     SetCheckedChangedInternal(aCheckedChanged);
2793   }
2794 }
2795 
SetCheckedChangedInternal(bool aCheckedChanged)2796 void HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged) {
2797   bool checkedChangedBefore = mCheckedChanged;
2798 
2799   mCheckedChanged = aCheckedChanged;
2800 
2801   // This method can't be called when we are not authorized to notify
2802   // so we do not need a aNotify parameter.
2803   if (checkedChangedBefore != aCheckedChanged) {
2804     UpdateState(true);
2805   }
2806 }
2807 
SetChecked(bool aChecked)2808 void HTMLInputElement::SetChecked(bool aChecked) {
2809   DoSetChecked(aChecked, true, true);
2810 }
2811 
DoSetChecked(bool aChecked,bool aNotify,bool aSetValueChanged)2812 void HTMLInputElement::DoSetChecked(bool aChecked, bool aNotify,
2813                                     bool aSetValueChanged) {
2814   // If the user or JS attempts to set checked, whether it actually changes the
2815   // value or not, we say the value was changed so that defaultValue don't
2816   // affect it no more.
2817   if (aSetValueChanged) {
2818     DoSetCheckedChanged(true, aNotify);
2819   }
2820 
2821   // Don't do anything if we're not changing whether it's checked (it would
2822   // screw up state actually, especially when you are setting radio button to
2823   // false)
2824   if (mChecked == aChecked) {
2825     return;
2826   }
2827 
2828   // Set checked
2829   if (mType != FormControlType::InputRadio) {
2830     SetCheckedInternal(aChecked, aNotify);
2831     return;
2832   }
2833 
2834   // For radio button, we need to do some extra fun stuff
2835   if (aChecked) {
2836     RadioSetChecked(aNotify);
2837     return;
2838   }
2839 
2840   nsIRadioGroupContainer* container = GetRadioGroupContainer();
2841   if (container) {
2842     nsAutoString name;
2843     GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
2844     container->SetCurrentRadioButton(name, nullptr);
2845   }
2846   // SetCheckedInternal is going to ask all radios to update their
2847   // validity state. We have to be sure the radio group container knows
2848   // the currently selected radio.
2849   SetCheckedInternal(false, aNotify);
2850 }
2851 
RadioSetChecked(bool aNotify)2852 void HTMLInputElement::RadioSetChecked(bool aNotify) {
2853   // Find the selected radio button so we can deselect it
2854   HTMLInputElement* currentlySelected = GetSelectedRadioButton();
2855 
2856   // Deselect the currently selected radio button
2857   if (currentlySelected) {
2858     // Pass true for the aNotify parameter since the currently selected
2859     // button is already in the document.
2860     currentlySelected->SetCheckedInternal(false, true);
2861   }
2862 
2863   // Let the group know that we are now the One True Radio Button
2864   nsIRadioGroupContainer* container = GetRadioGroupContainer();
2865   if (container) {
2866     nsAutoString name;
2867     GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
2868     container->SetCurrentRadioButton(name, this);
2869   }
2870 
2871   // SetCheckedInternal is going to ask all radios to update their
2872   // validity state.
2873   SetCheckedInternal(true, aNotify);
2874 }
2875 
GetRadioGroupContainer() const2876 nsIRadioGroupContainer* HTMLInputElement::GetRadioGroupContainer() const {
2877   NS_ASSERTION(
2878       mType == FormControlType::InputRadio,
2879       "GetRadioGroupContainer should only be called when type='radio'");
2880 
2881   nsAutoString name;
2882   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
2883 
2884   if (name.IsEmpty()) {
2885     return nullptr;
2886   }
2887 
2888   if (mForm) {
2889     return mForm;
2890   }
2891 
2892   if (IsInNativeAnonymousSubtree()) {
2893     return nullptr;
2894   }
2895 
2896   DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
2897   if (!docOrShadow) {
2898     return nullptr;
2899   }
2900 
2901   nsCOMPtr<nsIRadioGroupContainer> container =
2902       do_QueryInterface(&(docOrShadow->AsNode()));
2903   return container;
2904 }
2905 
GetSelectedRadioButton() const2906 HTMLInputElement* HTMLInputElement::GetSelectedRadioButton() const {
2907   nsIRadioGroupContainer* container = GetRadioGroupContainer();
2908   if (!container) {
2909     return nullptr;
2910   }
2911 
2912   nsAutoString name;
2913   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
2914 
2915   HTMLInputElement* selected = container->GetCurrentRadioButton(name);
2916   return selected;
2917 }
2918 
MaybeSubmitForm(nsPresContext * aPresContext)2919 nsresult HTMLInputElement::MaybeSubmitForm(nsPresContext* aPresContext) {
2920   if (!mForm) {
2921     // Nothing to do here.
2922     return NS_OK;
2923   }
2924 
2925   RefPtr<PresShell> presShell = aPresContext->GetPresShell();
2926   if (!presShell) {
2927     return NS_OK;
2928   }
2929 
2930   // Get the default submit element
2931   nsIFormControl* submitControl = mForm->GetDefaultSubmitElement();
2932   if (submitControl) {
2933     nsCOMPtr<nsIContent> submitContent = do_QueryInterface(submitControl);
2934     NS_ASSERTION(submitContent, "Form control not implementing nsIContent?!");
2935     // Fire the button's onclick handler and let the button handle
2936     // submitting the form.
2937     WidgetMouseEvent event(true, eMouseClick, nullptr, WidgetMouseEvent::eReal);
2938     nsEventStatus status = nsEventStatus_eIgnore;
2939     presShell->HandleDOMEventWithTarget(submitContent, &event, &status);
2940   } else if (!mForm->ImplicitSubmissionIsDisabled()) {
2941     // If there's only one text control, just submit the form
2942     // Hold strong ref across the event
2943     RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
2944     form->MaybeSubmit(nullptr);
2945   }
2946 
2947   return NS_OK;
2948 }
2949 
SetCheckedInternal(bool aChecked,bool aNotify)2950 void HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify) {
2951   // Set the value
2952   mChecked = aChecked;
2953 
2954   // Notify the frame
2955   if (mType == FormControlType::InputCheckbox ||
2956       mType == FormControlType::InputRadio) {
2957     nsIFrame* frame = GetPrimaryFrame();
2958     if (frame) {
2959       frame->InvalidateFrameSubtree();
2960     }
2961   }
2962 
2963   // No need to update element state, since we're about to call
2964   // UpdateState anyway.
2965   UpdateAllValidityStatesButNotElementState();
2966 
2967   // Notify the document that the CSS :checked pseudoclass for this element
2968   // has changed state.
2969   UpdateState(aNotify);
2970 
2971   // Notify all radios in the group that value has changed, this is to let
2972   // radios to have the chance to update its states, e.g., :indeterminate.
2973   if (mType == FormControlType::InputRadio) {
2974     nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
2975     VisitGroup(visitor);
2976   }
2977 }
2978 
Blur(ErrorResult & aError)2979 void HTMLInputElement::Blur(ErrorResult& aError) {
2980   if (CreatesDateTimeWidget()) {
2981     if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
2982       AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
2983           dateTimeBoxElement, u"MozBlurInnerTextBox"_ns, CanBubble::eNo,
2984           ChromeOnlyDispatch::eNo);
2985       dispatcher->RunDOMEventWhenSafe();
2986       return;
2987     }
2988   }
2989 
2990   nsGenericHTMLElement::Blur(aError);
2991 }
2992 
Focus(const FocusOptions & aOptions,CallerType aCallerType,ErrorResult & aError)2993 void HTMLInputElement::Focus(const FocusOptions& aOptions,
2994                              CallerType aCallerType, ErrorResult& aError) {
2995   if (CreatesDateTimeWidget()) {
2996     if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
2997       AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
2998           dateTimeBoxElement, u"MozFocusInnerTextBox"_ns, CanBubble::eNo,
2999           ChromeOnlyDispatch::eNo);
3000       dispatcher->RunDOMEventWhenSafe();
3001       return;
3002     }
3003   }
3004 
3005   nsGenericHTMLElement::Focus(aOptions, aCallerType, aError);
3006 }
3007 
3008 #if !defined(ANDROID) && !defined(XP_MACOSX)
IsNodeApzAwareInternal() const3009 bool HTMLInputElement::IsNodeApzAwareInternal() const {
3010   // Tell APZC we may handle mouse wheel event and do preventDefault when input
3011   // type is number.
3012   return mType == FormControlType::InputNumber ||
3013          mType == FormControlType::InputRange ||
3014          nsINode::IsNodeApzAwareInternal();
3015 }
3016 #endif
3017 
IsInteractiveHTMLContent() const3018 bool HTMLInputElement::IsInteractiveHTMLContent() const {
3019   return mType != FormControlType::InputHidden ||
3020          nsGenericHTMLFormElementWithState::IsInteractiveHTMLContent();
3021 }
3022 
AsyncEventRunning(AsyncEventDispatcher * aEvent)3023 void HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
3024   nsImageLoadingContent::AsyncEventRunning(aEvent);
3025 }
3026 
Select()3027 void HTMLInputElement::Select() {
3028   if (!IsSingleLineTextControl(false)) {
3029     return;
3030   }
3031 
3032   TextControlState* state = GetEditorState();
3033   MOZ_ASSERT(state, "Single line text controls are expected to have a state");
3034 
3035   if (FocusState() != eUnfocusable) {
3036     RefPtr<nsFrameSelection> fs = state->GetConstFrameSelection();
3037     if (fs && fs->MouseDownRecorded()) {
3038       // This means that we're being called while the frame selection has a
3039       // mouse down event recorded to adjust the caret during the mouse up
3040       // event. We are probably called from the focus event handler.  We should
3041       // override the delayed caret data in this case to ensure that this
3042       // select() call takes effect.
3043       fs->SetDelayedCaretData(nullptr);
3044     }
3045 
3046     if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
3047       fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
3048 
3049       // A focus event handler may change the type attribute, which will destroy
3050       // the previous state object.
3051       state = GetEditorState();
3052       if (!state) {
3053         return;
3054       }
3055     }
3056   }
3057 
3058   // Directly call TextControlState::SetSelectionRange because
3059   // HTMLInputElement::SetSelectionRange only applies to fewer types
3060   state->SetSelectionRange(0, UINT32_MAX, Optional<nsAString>(), IgnoreErrors(),
3061                            TextControlState::ScrollAfterSelection::No);
3062 }
3063 
DispatchSelectEvent(nsPresContext * aPresContext)3064 void HTMLInputElement::DispatchSelectEvent(nsPresContext* aPresContext) {
3065   // If already handling select event, don't dispatch a second.
3066   if (!mHandlingSelectEvent) {
3067     WidgetEvent event(true, eFormSelect);
3068 
3069     mHandlingSelectEvent = true;
3070     EventDispatcher::Dispatch(static_cast<nsIContent*>(this), aPresContext,
3071                               &event);
3072     mHandlingSelectEvent = false;
3073   }
3074 }
3075 
SelectAll(nsPresContext * aPresContext)3076 void HTMLInputElement::SelectAll(nsPresContext* aPresContext) {
3077   nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
3078 
3079   if (formControlFrame) {
3080     formControlFrame->SetFormProperty(nsGkAtoms::select, u""_ns);
3081   }
3082 }
3083 
NeedToInitializeEditorForEvent(EventChainPreVisitor & aVisitor) const3084 bool HTMLInputElement::NeedToInitializeEditorForEvent(
3085     EventChainPreVisitor& aVisitor) const {
3086   // We only need to initialize the editor for single line input controls
3087   // because they are lazily initialized.  We don't need to initialize the
3088   // control for certain types of events, because we know that those events are
3089   // safe to be handled without the editor being initialized.  These events
3090   // include: mousein/move/out, overflow/underflow, DOM mutation, and void
3091   // events. Void events are dispatched frequently by async keyboard scrolling
3092   // to focused elements, so it's important to handle them to prevent excessive
3093   // DOM mutations.
3094   if (!IsSingleLineTextControl(false) ||
3095       aVisitor.mEvent->mClass == eMutationEventClass) {
3096     return false;
3097   }
3098 
3099   switch (aVisitor.mEvent->mMessage) {
3100     case eVoidEvent:
3101     case eMouseMove:
3102     case eMouseEnterIntoWidget:
3103     case eMouseExitFromWidget:
3104     case eMouseOver:
3105     case eMouseOut:
3106     case eScrollPortUnderflow:
3107     case eScrollPortOverflow:
3108       return false;
3109     default:
3110       return true;
3111   }
3112 }
3113 
IsDisabledForEvents(WidgetEvent * aEvent)3114 bool HTMLInputElement::IsDisabledForEvents(WidgetEvent* aEvent) {
3115   return IsElementDisabledForEvents(aEvent, GetPrimaryFrame());
3116 }
3117 
GetEventTargetParent(EventChainPreVisitor & aVisitor)3118 void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
3119   // Do not process any DOM events if the element is disabled
3120   aVisitor.mCanHandle = false;
3121   if (IsDisabledForEvents(aVisitor.mEvent)) {
3122     return;
3123   }
3124 
3125   // Initialize the editor if needed.
3126   if (NeedToInitializeEditorForEvent(aVisitor)) {
3127     nsITextControlFrame* textControlFrame = do_QueryFrame(GetPrimaryFrame());
3128     if (textControlFrame) textControlFrame->EnsureEditorInitialized();
3129   }
3130 
3131   //
3132   // Web pages expect the value of a radio button or checkbox to be set
3133   // *before* onclick and DOMActivate fire, and they expect that if they set
3134   // the value explicitly during onclick or DOMActivate it will not be toggled
3135   // or any such nonsense.
3136   // In order to support that (bug 57137 and 58460 are examples) we toggle
3137   // the checked attribute *first*, and then fire onclick.  If the user
3138   // returns false, we reset the control to the old checked value.  Otherwise,
3139   // we dispatch DOMActivate.  If DOMActivate is cancelled, we also reset
3140   // the control to the old checked value.  We need to keep track of whether
3141   // we've already toggled the state from onclick since the user could
3142   // explicitly dispatch DOMActivate on the element.
3143   //
3144   // These are compatibility hacks and are defined as legacy-pre-activation
3145   // and legacy-canceled-activation behavior in HTML.
3146   //
3147 
3148   // Track whether we're in the outermost Dispatch invocation that will
3149   // cause activation of the input.  That is, if we're a click event, or a
3150   // DOMActivate that was dispatched directly, this will be set, but if we're
3151   // a DOMActivate dispatched from click handling, it will not be set.
3152   WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3153   bool outerActivateEvent = ((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
3154                              (aVisitor.mEvent->mMessage == eLegacyDOMActivate &&
3155                               !mInInternalActivate));
3156 
3157   if (outerActivateEvent) {
3158     aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
3159   }
3160 
3161   bool originalCheckedValue = false;
3162 
3163   if (outerActivateEvent) {
3164     mCheckedIsToggled = false;
3165 
3166     switch (mType) {
3167       case FormControlType::InputCheckbox: {
3168         if (mIndeterminate) {
3169           // indeterminate is always set to FALSE when the checkbox is toggled
3170           SetIndeterminateInternal(false, false);
3171           aVisitor.mItemFlags |= NS_ORIGINAL_INDETERMINATE_VALUE;
3172         }
3173 
3174         originalCheckedValue = Checked();
3175         DoSetChecked(!originalCheckedValue, true, true);
3176         mCheckedIsToggled = true;
3177       } break;
3178 
3179       case FormControlType::InputRadio: {
3180         HTMLInputElement* selectedRadioButton = GetSelectedRadioButton();
3181         aVisitor.mItemData = static_cast<Element*>(selectedRadioButton);
3182 
3183         originalCheckedValue = mChecked;
3184         if (!originalCheckedValue) {
3185           DoSetChecked(true, true, true);
3186           mCheckedIsToggled = true;
3187         }
3188       } break;
3189 
3190       case FormControlType::InputSubmit:
3191       case FormControlType::InputImage:
3192         if (mForm && !aVisitor.mEvent->mFlags.mMultiplePreActionsPrevented) {
3193           // Make sure other submit elements don't try to trigger submission.
3194           aVisitor.mEvent->mFlags.mMultiplePreActionsPrevented = true;
3195           aVisitor.mItemFlags |= NS_IN_SUBMIT_CLICK;
3196           // tell the form that we are about to enter a click handler.
3197           // that means that if there are scripted submissions, the
3198           // latest one will be deferred until after the exit point of the
3199           // handler.
3200           mForm->OnSubmitClickBegin(this);
3201         }
3202         break;
3203 
3204       default:
3205         break;
3206     }
3207   }
3208 
3209   if (originalCheckedValue) {
3210     aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
3211   }
3212 
3213   // We must cache type because mType may change during JS event (bug 2369)
3214   aVisitor.mItemFlags |= uint8_t(mType);
3215 
3216   if (aVisitor.mEvent->mMessage == eFocus && aVisitor.mEvent->IsTrusted() &&
3217       MayFireChangeOnBlur() &&
3218       // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
3219       // we get the 'focus' event.
3220       !mIsDraggingRange) {
3221     GetValue(mFocusedValue, CallerType::System);
3222   }
3223 
3224   // Fire onchange (if necessary), before we do the blur, bug 357684.
3225   if (aVisitor.mEvent->mMessage == eBlur) {
3226     // We set NS_PRE_HANDLE_BLUR_EVENT here and handle it in PreHandleEvent to
3227     // prevent breaking event target chain creation.
3228     aVisitor.mWantsPreHandleEvent = true;
3229     aVisitor.mItemFlags |= NS_PRE_HANDLE_BLUR_EVENT;
3230   }
3231 
3232   if (mType == FormControlType::InputRange &&
3233       (aVisitor.mEvent->mMessage == eFocus ||
3234        aVisitor.mEvent->mMessage == eBlur)) {
3235     // Just as nsGenericHTMLFormElementWithState::GetEventTargetParent calls
3236     // nsIFormControlFrame::SetFocus, we handle focus here.
3237     nsIFrame* frame = GetPrimaryFrame();
3238     if (frame) {
3239       frame->InvalidateFrameSubtree();
3240     }
3241   }
3242 
3243   if (CreatesDateTimeWidget() && aVisitor.mEvent->mMessage == eFocus &&
3244       aVisitor.mEvent->mOriginalTarget == this) {
3245     // If original target is this and not the inner text control, we should
3246     // pass the focus to the inner text control.
3247     if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
3248       AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
3249           dateTimeBoxElement, u"MozFocusInnerTextBox"_ns, CanBubble::eNo,
3250           ChromeOnlyDispatch::eNo);
3251       dispatcher->RunDOMEventWhenSafe();
3252     }
3253   }
3254 
3255   if (mType == FormControlType::InputNumber && aVisitor.mEvent->IsTrusted()) {
3256     if (mNumberControlSpinnerIsSpinning) {
3257       // If the timer is running the user has depressed the mouse on one of the
3258       // spin buttons. If the mouse exits the button we either want to reverse
3259       // the direction of spin if it has moved over the other button, or else
3260       // we want to end the spin. We do this here (rather than in
3261       // PostHandleEvent) because we don't want to let content preventDefault()
3262       // the end of the spin.
3263       if (aVisitor.mEvent->mMessage == eMouseMove) {
3264         // Be aggressive about stopping the spin:
3265         bool stopSpin = true;
3266         nsNumberControlFrame* numberControlFrame =
3267             do_QueryFrame(GetPrimaryFrame());
3268         if (numberControlFrame) {
3269           bool oldNumberControlSpinTimerSpinsUpValue =
3270               mNumberControlSpinnerSpinsUp;
3271           switch (numberControlFrame->GetSpinButtonForPointerEvent(
3272               aVisitor.mEvent->AsMouseEvent())) {
3273             case nsNumberControlFrame::eSpinButtonUp:
3274               mNumberControlSpinnerSpinsUp = true;
3275               stopSpin = false;
3276               break;
3277             case nsNumberControlFrame::eSpinButtonDown:
3278               mNumberControlSpinnerSpinsUp = false;
3279               stopSpin = false;
3280               break;
3281           }
3282           if (mNumberControlSpinnerSpinsUp !=
3283               oldNumberControlSpinTimerSpinsUpValue) {
3284             nsNumberControlFrame* numberControlFrame =
3285                 do_QueryFrame(GetPrimaryFrame());
3286             if (numberControlFrame) {
3287               numberControlFrame->SpinnerStateChanged();
3288             }
3289           }
3290         }
3291         if (stopSpin) {
3292           StopNumberControlSpinnerSpin();
3293         }
3294       } else if (aVisitor.mEvent->mMessage == eMouseUp) {
3295         StopNumberControlSpinnerSpin();
3296       }
3297     }
3298   }
3299 
3300   nsGenericHTMLFormElementWithState::GetEventTargetParent(aVisitor);
3301 
3302   // Stop the event if the related target's first non-native ancestor is the
3303   // same as the original target's first non-native ancestor (we are moving
3304   // inside of the same element).
3305   //
3306   // FIXME(emilio): Is this still needed now that we use Shadow DOM for this?
3307   if (CreatesDateTimeWidget() && aVisitor.mEvent->IsTrusted() &&
3308       (aVisitor.mEvent->mMessage == eFocus ||
3309        aVisitor.mEvent->mMessage == eFocusIn ||
3310        aVisitor.mEvent->mMessage == eFocusOut ||
3311        aVisitor.mEvent->mMessage == eBlur)) {
3312     nsCOMPtr<nsIContent> originalTarget =
3313         do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mOriginalTarget);
3314     nsCOMPtr<nsIContent> relatedTarget =
3315         do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
3316 
3317     if (originalTarget && relatedTarget &&
3318         originalTarget->FindFirstNonChromeOnlyAccessContent() ==
3319             relatedTarget->FindFirstNonChromeOnlyAccessContent()) {
3320       aVisitor.mCanHandle = false;
3321     }
3322   }
3323 }
3324 
PreHandleEvent(EventChainVisitor & aVisitor)3325 nsresult HTMLInputElement::PreHandleEvent(EventChainVisitor& aVisitor) {
3326   if (aVisitor.mItemFlags & NS_PRE_HANDLE_BLUR_EVENT) {
3327     MOZ_ASSERT(aVisitor.mEvent->mMessage == eBlur);
3328     FireChangeEventIfNeeded();
3329   }
3330   return nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
3331 }
3332 
StartRangeThumbDrag(WidgetGUIEvent * aEvent)3333 void HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent) {
3334   nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
3335   if (!rangeFrame) {
3336     return;
3337   }
3338 
3339   mIsDraggingRange = true;
3340   mRangeThumbDragStartValue = GetValueAsDecimal();
3341   // Don't use CaptureFlags::RetargetToElement, as that breaks pseudo-class
3342   // styling of the thumb.
3343   PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
3344 
3345   // Before we change the value, record the current value so that we'll
3346   // correctly send a 'change' event if appropriate. We need to do this here
3347   // because the 'focus' event is handled after the 'mousedown' event that
3348   // we're being called for (i.e. too late to update mFocusedValue, since we'll
3349   // have changed it by then).
3350   GetValue(mFocusedValue, CallerType::System);
3351 
3352   SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
3353 }
3354 
FinishRangeThumbDrag(WidgetGUIEvent * aEvent)3355 void HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent* aEvent) {
3356   MOZ_ASSERT(mIsDraggingRange);
3357 
3358   if (PresShell::GetCapturingContent() == this) {
3359     PresShell::ReleaseCapturingContent();
3360   }
3361   if (aEvent) {
3362     nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
3363     SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
3364   }
3365   mIsDraggingRange = false;
3366   FireChangeEventIfNeeded();
3367 }
3368 
CancelRangeThumbDrag(bool aIsForUserEvent)3369 void HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent) {
3370   MOZ_ASSERT(mIsDraggingRange);
3371 
3372   mIsDraggingRange = false;
3373   if (PresShell::GetCapturingContent() == this) {
3374     PresShell::ReleaseCapturingContent();
3375   }
3376   if (aIsForUserEvent) {
3377     SetValueOfRangeForUserEvent(mRangeThumbDragStartValue);
3378   } else {
3379     // Don't dispatch an 'input' event - at least not using
3380     // DispatchTrustedEvent.
3381     // TODO: decide what we should do here - bug 851782.
3382     nsAutoString val;
3383     mInputType->ConvertNumberToString(mRangeThumbDragStartValue, val);
3384     // TODO: What should we do if SetValueInternal fails?  (The allocation
3385     // is small, so we should be fine here.)
3386     SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
3387                            ValueSetterOption::SetValueChanged});
3388     nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
3389     if (frame) {
3390       frame->UpdateForValueChange();
3391     }
3392     DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3393     NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3394                          "Failed to dispatch input event");
3395   }
3396 }
3397 
SetValueOfRangeForUserEvent(Decimal aValue)3398 void HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue) {
3399   MOZ_ASSERT(aValue.isFinite());
3400 
3401   Decimal oldValue = GetValueAsDecimal();
3402 
3403   nsAutoString val;
3404   mInputType->ConvertNumberToString(aValue, val);
3405   // TODO: What should we do if SetValueInternal fails?  (The allocation
3406   // is small, so we should be fine here.)
3407   SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
3408                          ValueSetterOption::SetValueChanged});
3409   nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
3410   if (frame) {
3411     frame->UpdateForValueChange();
3412   }
3413 
3414   if (GetValueAsDecimal() != oldValue) {
3415     DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3416     NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3417                          "Failed to dispatch input event");
3418   }
3419 }
3420 
StartNumberControlSpinnerSpin()3421 void HTMLInputElement::StartNumberControlSpinnerSpin() {
3422   MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
3423 
3424   mNumberControlSpinnerIsSpinning = true;
3425 
3426   nsRepeatService::GetInstance()->Start(
3427       HandleNumberControlSpin, this, OwnerDoc(), "HandleNumberControlSpin"_ns);
3428 
3429   // Capture the mouse so that we can tell if the pointer moves from one
3430   // spin button to the other, or to some other element:
3431   PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
3432 
3433   nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
3434   if (numberControlFrame) {
3435     numberControlFrame->SpinnerStateChanged();
3436   }
3437 }
3438 
StopNumberControlSpinnerSpin(SpinnerStopState aState)3439 void HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState) {
3440   if (mNumberControlSpinnerIsSpinning) {
3441     if (PresShell::GetCapturingContent() == this) {
3442       PresShell::ReleaseCapturingContent();
3443     }
3444 
3445     nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
3446 
3447     mNumberControlSpinnerIsSpinning = false;
3448 
3449     if (aState == eAllowDispatchingEvents) {
3450       FireChangeEventIfNeeded();
3451     }
3452 
3453     nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
3454     if (numberControlFrame) {
3455       MOZ_ASSERT(aState == eAllowDispatchingEvents,
3456                  "Shouldn't have primary frame for the element when we're not "
3457                  "allowed to dispatch events to it anymore.");
3458       numberControlFrame->SpinnerStateChanged();
3459     }
3460   }
3461 }
3462 
StepNumberControlForUserEvent(int32_t aDirection)3463 void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
3464   // We can't use GetValidityState here because the validity state is not set
3465   // if the user hasn't previously taken an action to set or change the value,
3466   // according to the specs.
3467   if (HasBadInput()) {
3468     // If the user has typed a value into the control and inadvertently made a
3469     // mistake (e.g. put a thousand separator at the wrong point) we do not
3470     // want to wipe out what they typed if they try to increment/decrement the
3471     // value. Better is to highlight the value as being invalid so that they
3472     // can correct what they typed.
3473     // We only do this if there actually is a value typed in by/displayed to
3474     // the user. (IsValid() can return false if the 'required' attribute is
3475     // set and the value is the empty string.)
3476     if (!IsValueEmpty()) {
3477       // We pass 'true' for UpdateValidityUIBits' aIsFocused argument
3478       // regardless because we need the UI to update _now_ or the user will
3479       // wonder why the step behavior isn't functioning.
3480       UpdateValidityUIBits(true);
3481       UpdateState(true);
3482       return;
3483     }
3484   }
3485 
3486   Decimal newValue = Decimal::nan();  // unchanged if value will not change
3487 
3488   nsresult rv = GetValueIfStepped(aDirection, CALLED_FOR_USER_EVENT, &newValue);
3489 
3490   if (NS_FAILED(rv) || !newValue.isFinite()) {
3491     return;  // value should not or will not change
3492   }
3493 
3494   nsAutoString newVal;
3495   mInputType->ConvertNumberToString(newValue, newVal);
3496   // TODO: What should we do if SetValueInternal fails?  (The allocation
3497   // is small, so we should be fine here.)
3498   SetValueInternal(newVal, {ValueSetterOption::BySetUserInputAPI,
3499                             ValueSetterOption::SetValueChanged});
3500 }
3501 
SelectTextFieldOnFocus()3502 static bool SelectTextFieldOnFocus() {
3503   if (!gSelectTextFieldOnFocus) {
3504     int32_t selectTextfieldsOnKeyFocus = -1;
3505     nsresult rv =
3506         LookAndFeel::GetInt(LookAndFeel::IntID::SelectTextfieldsOnKeyFocus,
3507                             &selectTextfieldsOnKeyFocus);
3508     if (NS_FAILED(rv)) {
3509       gSelectTextFieldOnFocus = -1;
3510     } else {
3511       gSelectTextFieldOnFocus = selectTextfieldsOnKeyFocus != 0 ? 1 : -1;
3512     }
3513   }
3514 
3515   return gSelectTextFieldOnFocus == 1;
3516 }
3517 
ShouldPreventDOMActivateDispatch(EventTarget * aOriginalTarget)3518 bool HTMLInputElement::ShouldPreventDOMActivateDispatch(
3519     EventTarget* aOriginalTarget) {
3520   /*
3521    * For the moment, there is only one situation where we actually want to
3522    * prevent firing a DOMActivate event:
3523    *  - we are a <input type='file'> that just got a click event,
3524    *  - the event was targeted to our button which should have sent a
3525    *    DOMActivate event.
3526    */
3527 
3528   if (mType != FormControlType::InputFile) {
3529     return false;
3530   }
3531 
3532   nsCOMPtr<Element> target = do_QueryInterface(aOriginalTarget);
3533   if (!target) {
3534     return false;
3535   }
3536 
3537   return target->GetParent() == this &&
3538          target->IsRootOfNativeAnonymousSubtree() &&
3539          target->IsHTMLElement(nsGkAtoms::button);
3540 }
3541 
MaybeInitPickers(EventChainPostVisitor & aVisitor)3542 nsresult HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor) {
3543   // Open a file picker when we receive a click on a <input type='file'>, or
3544   // open a color picker when we receive a click on a <input type='color'>.
3545   // A click is handled in the following cases:
3546   // - preventDefault() has not been called (or something similar);
3547   // - it's the left mouse button.
3548   // We do not prevent non-trusted click because authors can already use
3549   // .click(). However, the pickers will follow the rules of popup-blocking.
3550   if (aVisitor.mEvent->DefaultPrevented()) {
3551     return NS_OK;
3552   }
3553   WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3554   if (!(mouseEvent && mouseEvent->IsLeftClickEvent())) {
3555     return NS_OK;
3556   }
3557   if (mType == FormControlType::InputFile) {
3558     // If the user clicked on the "Choose folder..." button we open the
3559     // directory picker, else we open the file picker.
3560     FilePickerType type = FILE_PICKER_FILE;
3561     nsCOMPtr<nsIContent> target =
3562         do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
3563     if (target && target->FindFirstNonChromeOnlyAccessContent() == this &&
3564         ((StaticPrefs::dom_input_dirpicker() && Allowdirs()) ||
3565          (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
3566           HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)))) {
3567       type = FILE_PICKER_DIRECTORY;
3568     }
3569     return InitFilePicker(type);
3570   }
3571   if (mType == FormControlType::InputColor) {
3572     return InitColorPicker();
3573   }
3574 
3575   return NS_OK;
3576 }
3577 
3578 /**
3579  * Return true if the input event should be ignored because of its modifiers.
3580  * Control is treated specially, since sometimes we ignore it, and sometimes
3581  * we don't (for webcompat reasons).
3582  */
IgnoreInputEventWithModifier(const WidgetInputEvent & aEvent,bool ignoreControl)3583 static bool IgnoreInputEventWithModifier(const WidgetInputEvent& aEvent,
3584                                          bool ignoreControl) {
3585   return (ignoreControl && aEvent.IsControl()) || aEvent.IsAltGraph() ||
3586          aEvent.IsFn() || aEvent.IsOS();
3587 }
3588 
StepsInputValue(const WidgetKeyboardEvent & aEvent) const3589 bool HTMLInputElement::StepsInputValue(
3590     const WidgetKeyboardEvent& aEvent) const {
3591   if (mType != FormControlType::InputNumber) {
3592     return false;
3593   }
3594   if (aEvent.mMessage != eKeyPress) {
3595     return false;
3596   }
3597   if (!aEvent.IsTrusted()) {
3598     return false;
3599   }
3600   if (aEvent.mKeyCode != NS_VK_UP && aEvent.mKeyCode != NS_VK_DOWN) {
3601     return false;
3602   }
3603   if (IgnoreInputEventWithModifier(aEvent, false)) {
3604     return false;
3605   }
3606   if (aEvent.DefaultPrevented()) {
3607     return false;
3608   }
3609   if (!IsMutable()) {
3610     return false;
3611   }
3612   return true;
3613 }
3614 
ActivatesWithKeyboard(FormControlType aType)3615 static bool ActivatesWithKeyboard(FormControlType aType) {
3616   switch (aType) {
3617     case FormControlType::InputCheckbox:
3618     case FormControlType::InputRadio:
3619     case FormControlType::InputButton:
3620     case FormControlType::InputReset:
3621     case FormControlType::InputSubmit:
3622     case FormControlType::InputFile:
3623     case FormControlType::InputImage:  // Bug 34418
3624     case FormControlType::InputColor:
3625       return true;
3626     default:
3627       return false;
3628   }
3629 }
3630 
PostHandleEvent(EventChainPostVisitor & aVisitor)3631 nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
3632   if (aVisitor.mEvent->mMessage == eFocus ||
3633       aVisitor.mEvent->mMessage == eBlur) {
3634     if (aVisitor.mEvent->mMessage == eBlur) {
3635       if (mIsDraggingRange) {
3636         FinishRangeThumbDrag();
3637       } else if (mNumberControlSpinnerIsSpinning) {
3638         StopNumberControlSpinnerSpin();
3639       }
3640     }
3641 
3642     UpdateValidityUIBits(aVisitor.mEvent->mMessage == eFocus);
3643 
3644     UpdateState(true);
3645   }
3646 
3647   nsresult rv = NS_OK;
3648   bool outerActivateEvent = !!(aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT);
3649   bool originalCheckedValue =
3650       !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
3651   auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
3652 
3653   // Ideally we would make the default action for click and space just dispatch
3654   // DOMActivate, and the default action for DOMActivate flip the checkbox/
3655   // radio state and fire onchange.  However, for backwards compatibility, we
3656   // need to flip the state before firing click, and we need to fire click
3657   // when space is pressed.  So, we just nest the firing of DOMActivate inside
3658   // the click event handling, and allow cancellation of DOMActivate to cancel
3659   // the click.
3660   if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault &&
3661       !IsSingleLineTextControl(true) && mType != FormControlType::InputNumber) {
3662     WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3663     if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
3664         !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->mOriginalTarget)) {
3665       // DOMActive event should be trusted since the activation is actually
3666       // occurred even if the cause is an untrusted click event.
3667       InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
3668       actEvent.mDetail = 1;
3669 
3670       if (RefPtr<PresShell> presShell =
3671               aVisitor.mPresContext ? aVisitor.mPresContext->GetPresShell()
3672                                     : nullptr) {
3673         nsEventStatus status = nsEventStatus_eIgnore;
3674         mInInternalActivate = true;
3675         rv = presShell->HandleDOMEventWithTarget(this, &actEvent, &status);
3676         mInInternalActivate = false;
3677 
3678         // If activate is cancelled, we must do the same as when click is
3679         // cancelled (revert the checkbox to its original value).
3680         if (status == nsEventStatus_eConsumeNoDefault) {
3681           aVisitor.mEventStatus = status;
3682         }
3683       }
3684     }
3685   }
3686 
3687   if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) && mForm) {
3688     switch (oldType) {
3689       case FormControlType::InputSubmit:
3690       case FormControlType::InputImage:
3691         // tell the form that we are about to exit a click handler
3692         // so the form knows not to defer subsequent submissions
3693         // the pending ones that were created during the handler
3694         // will be flushed or forgotten.
3695         mForm->OnSubmitClickEnd();
3696         break;
3697       default:
3698         break;
3699     }
3700   }
3701 
3702   bool preventDefault =
3703       aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault;
3704   if (IsDisabled() && oldType != FormControlType::InputCheckbox &&
3705       oldType != FormControlType::InputRadio) {
3706     // Behave as if defaultPrevented when the element becomes disabled by event
3707     // listeners. Checkboxes and radio buttons should still process clicks for
3708     // web compat. See:
3709     // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
3710     preventDefault = true;
3711   }
3712 
3713   // now check to see if the event was canceled
3714   if (mCheckedIsToggled && outerActivateEvent) {
3715     if (preventDefault) {
3716       // if it was canceled and a radio button, then set the old
3717       // selected btn to TRUE. if it is a checkbox then set it to its
3718       // original value (legacy-canceled-activation)
3719       if (oldType == FormControlType::InputRadio) {
3720         nsCOMPtr<nsIContent> content = do_QueryInterface(aVisitor.mItemData);
3721         HTMLInputElement* selectedRadioButton =
3722             HTMLInputElement::FromNodeOrNull(content);
3723         if (selectedRadioButton) {
3724           selectedRadioButton->SetChecked(true);
3725         }
3726         // If there was no checked radio button or this one is no longer a
3727         // radio button we must reset it back to false to cancel the action.
3728         // See how the web of hack grows?
3729         if (!selectedRadioButton || mType != FormControlType::InputRadio) {
3730           DoSetChecked(false, true, true);
3731         }
3732       } else if (oldType == FormControlType::InputCheckbox) {
3733         bool originalIndeterminateValue =
3734             !!(aVisitor.mItemFlags & NS_ORIGINAL_INDETERMINATE_VALUE);
3735         SetIndeterminateInternal(originalIndeterminateValue, false);
3736         DoSetChecked(originalCheckedValue, true, true);
3737       }
3738     } else {
3739       // Fire input event and then change event.
3740       DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3741       NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3742                            "Failed to dispatch input event");
3743 
3744       nsContentUtils::DispatchTrustedEvent<WidgetEvent>(
3745           OwnerDoc(), static_cast<Element*>(this), eFormChange, CanBubble::eYes,
3746           Cancelable::eNo);
3747 #ifdef ACCESSIBILITY
3748       // Fire an event to notify accessibility
3749       if (mType == FormControlType::InputCheckbox) {
3750         FireEventForAccessibility(this, eFormCheckboxStateChange);
3751       } else {
3752         FireEventForAccessibility(this, eFormRadioStateChange);
3753         // Fire event for the previous selected radio.
3754         nsCOMPtr<nsIContent> content = do_QueryInterface(aVisitor.mItemData);
3755         HTMLInputElement* previous = HTMLInputElement::FromNodeOrNull(content);
3756         if (previous) {
3757           FireEventForAccessibility(previous, eFormRadioStateChange);
3758         }
3759       }
3760 #endif
3761     }
3762   }
3763 
3764   if (NS_SUCCEEDED(rv)) {
3765     WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
3766     if (keyEvent && StepsInputValue(*keyEvent)) {
3767       StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
3768       FireChangeEventIfNeeded();
3769       aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3770     } else if (!preventDefault) {
3771       // Checkbox and Radio try to submit on Enter press
3772       if (aVisitor.mEvent->mMessage == eKeyPress &&
3773           (mType == FormControlType::InputCheckbox ||
3774            mType == FormControlType::InputRadio) &&
3775           keyEvent->mKeyCode == NS_VK_RETURN && aVisitor.mPresContext) {
3776         MaybeSubmitForm(aVisitor.mPresContext);
3777       } else if (ActivatesWithKeyboard(mType)) {
3778         // Otherwise we maybe dispatch a synthesized click.
3779         HandleKeyboardActivation(aVisitor);
3780       }
3781 
3782       switch (aVisitor.mEvent->mMessage) {
3783         case eFocus: {
3784           // see if we should select the contents of the textbox. This happens
3785           // for text and password fields when the field was focused by the
3786           // keyboard or a navigation, the platform allows it, and it wasn't
3787           // just because we raised a window.
3788           //
3789           // While it'd usually make sense, we don't do this for JS callers
3790           // because it causes some compat issues, see bug 1712724 for example.
3791           nsFocusManager* fm = nsFocusManager::GetFocusManager();
3792           if (fm && IsSingleLineTextControl(false) &&
3793               !aVisitor.mEvent->AsFocusEvent()->mFromRaise &&
3794               SelectTextFieldOnFocus()) {
3795             if (Document* document = GetComposedDoc()) {
3796               uint32_t lastFocusMethod;
3797               fm->GetLastFocusMethod(document->GetWindow(), &lastFocusMethod);
3798               if (lastFocusMethod & (nsIFocusManager::FLAG_BYKEY |
3799                                      nsIFocusManager::FLAG_BYMOVEFOCUS) &&
3800                   !(lastFocusMethod & nsIFocusManager::FLAG_BYJS)) {
3801                 RefPtr<nsPresContext> presContext =
3802                     GetPresContext(eForComposedDoc);
3803                 DispatchSelectEvent(presContext);
3804                 SelectAll(presContext);
3805               }
3806             }
3807           }
3808           break;
3809         }
3810         case eKeyPress: {
3811           if (mType == FormControlType::InputRadio && !keyEvent->IsAlt() &&
3812               !keyEvent->IsControl() && !keyEvent->IsMeta()) {
3813             bool isMovingBack = false;
3814             switch (keyEvent->mKeyCode) {
3815               case NS_VK_UP:
3816               case NS_VK_LEFT:
3817                 isMovingBack = true;
3818                 [[fallthrough]];
3819               case NS_VK_DOWN:
3820               case NS_VK_RIGHT:
3821                 // Arrow key pressed, focus+select prev/next radio button
3822                 nsIRadioGroupContainer* container = GetRadioGroupContainer();
3823                 if (container) {
3824                   nsAutoString name;
3825                   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
3826                   RefPtr<HTMLInputElement> selectedRadioButton;
3827                   container->GetNextRadioButton(
3828                       name, isMovingBack, this,
3829                       getter_AddRefs(selectedRadioButton));
3830                   if (selectedRadioButton) {
3831                     FocusOptions options;
3832                     ErrorResult error;
3833 
3834                     selectedRadioButton->Focus(options, CallerType::System,
3835                                                error);
3836                     rv = error.StealNSResult();
3837                     if (NS_SUCCEEDED(rv)) {
3838                       rv = DispatchSimulatedClick(selectedRadioButton,
3839                                                   aVisitor.mEvent->IsTrusted(),
3840                                                   aVisitor.mPresContext);
3841                       if (NS_SUCCEEDED(rv)) {
3842                         aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3843                       }
3844                     }
3845                   }
3846                 }
3847             }
3848           }
3849 
3850           /*
3851            * For some input types, if the user hits enter, the form is
3852            * submitted.
3853            *
3854            * Bug 99920, bug 109463 and bug 147850:
3855            * (a) if there is a submit control in the form, click the first
3856            *     submit control in the form.
3857            * (b) if there is just one text control in the form, submit by
3858            *     sending a submit event directly to the form
3859            * (c) if there is more than one text input and no submit buttons, do
3860            *     not submit, period.
3861            */
3862 
3863           if (keyEvent->mKeyCode == NS_VK_RETURN &&
3864               (IsSingleLineTextControl(false, mType) ||
3865                mType == FormControlType::InputNumber ||
3866                IsDateTimeInputType(mType))) {
3867             FireChangeEventIfNeeded();
3868             if (aVisitor.mPresContext) {
3869               rv = MaybeSubmitForm(aVisitor.mPresContext);
3870               NS_ENSURE_SUCCESS(rv, rv);
3871             }
3872           }
3873 
3874           if (mType == FormControlType::InputRange && !keyEvent->IsAlt() &&
3875               !keyEvent->IsControl() && !keyEvent->IsMeta() &&
3876               (keyEvent->mKeyCode == NS_VK_LEFT ||
3877                keyEvent->mKeyCode == NS_VK_RIGHT ||
3878                keyEvent->mKeyCode == NS_VK_UP ||
3879                keyEvent->mKeyCode == NS_VK_DOWN ||
3880                keyEvent->mKeyCode == NS_VK_PAGE_UP ||
3881                keyEvent->mKeyCode == NS_VK_PAGE_DOWN ||
3882                keyEvent->mKeyCode == NS_VK_HOME ||
3883                keyEvent->mKeyCode == NS_VK_END)) {
3884             Decimal minimum = GetMinimum();
3885             Decimal maximum = GetMaximum();
3886             MOZ_ASSERT(minimum.isFinite() && maximum.isFinite());
3887             if (minimum < maximum) {  // else the value is locked to the minimum
3888               Decimal value = GetValueAsDecimal();
3889               Decimal step = GetStep();
3890               if (step == kStepAny) {
3891                 step = GetDefaultStep();
3892               }
3893               MOZ_ASSERT(value.isFinite() && step.isFinite());
3894               Decimal newValue;
3895               switch (keyEvent->mKeyCode) {
3896                 case NS_VK_LEFT:
3897                   newValue =
3898                       value +
3899                       (GetComputedDirectionality() == eDir_RTL ? step : -step);
3900                   break;
3901                 case NS_VK_RIGHT:
3902                   newValue =
3903                       value +
3904                       (GetComputedDirectionality() == eDir_RTL ? -step : step);
3905                   break;
3906                 case NS_VK_UP:
3907                   // Even for horizontal range, "up" means "increase"
3908                   newValue = value + step;
3909                   break;
3910                 case NS_VK_DOWN:
3911                   // Even for horizontal range, "down" means "decrease"
3912                   newValue = value - step;
3913                   break;
3914                 case NS_VK_HOME:
3915                   newValue = minimum;
3916                   break;
3917                 case NS_VK_END:
3918                   newValue = maximum;
3919                   break;
3920                 case NS_VK_PAGE_UP:
3921                   // For PgUp/PgDn we jump 10% of the total range, unless step
3922                   // requires us to jump more.
3923                   newValue =
3924                       value + std::max(step, (maximum - minimum) / Decimal(10));
3925                   break;
3926                 case NS_VK_PAGE_DOWN:
3927                   newValue =
3928                       value - std::max(step, (maximum - minimum) / Decimal(10));
3929                   break;
3930               }
3931               SetValueOfRangeForUserEvent(newValue);
3932               FireChangeEventIfNeeded();
3933               aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3934             }
3935           }
3936 
3937         } break;  // eKeyPress
3938 
3939         case eMouseDown:
3940         case eMouseUp:
3941         case eMouseDoubleClick: {
3942           // cancel all of these events for buttons
3943           // XXXsmaug Why?
3944           WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3945           if (mouseEvent->mButton == MouseButton::eMiddle ||
3946               mouseEvent->mButton == MouseButton::eSecondary) {
3947             if (mType == FormControlType::InputButton ||
3948                 mType == FormControlType::InputReset ||
3949                 mType == FormControlType::InputSubmit) {
3950               if (aVisitor.mDOMEvent) {
3951                 aVisitor.mDOMEvent->StopPropagation();
3952               } else {
3953                 rv = NS_ERROR_FAILURE;
3954               }
3955             }
3956           }
3957           if (mType == FormControlType::InputNumber &&
3958               aVisitor.mEvent->IsTrusted()) {
3959             if (mouseEvent->mButton == MouseButton::ePrimary &&
3960                 !IgnoreInputEventWithModifier(*mouseEvent, false)) {
3961               nsNumberControlFrame* numberControlFrame =
3962                   do_QueryFrame(GetPrimaryFrame());
3963               if (numberControlFrame) {
3964                 if (aVisitor.mEvent->mMessage == eMouseDown && IsMutable()) {
3965                   switch (numberControlFrame->GetSpinButtonForPointerEvent(
3966                       aVisitor.mEvent->AsMouseEvent())) {
3967                     case nsNumberControlFrame::eSpinButtonUp:
3968                       StepNumberControlForUserEvent(1);
3969                       mNumberControlSpinnerSpinsUp = true;
3970                       StartNumberControlSpinnerSpin();
3971                       aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3972                       break;
3973                     case nsNumberControlFrame::eSpinButtonDown:
3974                       StepNumberControlForUserEvent(-1);
3975                       mNumberControlSpinnerSpinsUp = false;
3976                       StartNumberControlSpinnerSpin();
3977                       aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3978                       break;
3979                   }
3980                 }
3981               }
3982             }
3983             if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
3984               // We didn't handle this to step up/down. Whatever this was, be
3985               // aggressive about stopping the spin. (And don't set
3986               // nsEventStatus_eConsumeNoDefault after doing so, since that
3987               // might prevent, say, the context menu from opening.)
3988               StopNumberControlSpinnerSpin();
3989             }
3990           }
3991           break;
3992         }
3993 #if !defined(ANDROID) && !defined(XP_MACOSX)
3994         case eWheel: {
3995           // Handle wheel events as increasing / decreasing the input element's
3996           // value when it's focused and it's type is number or range.
3997           WidgetWheelEvent* wheelEvent = aVisitor.mEvent->AsWheelEvent();
3998           if (!aVisitor.mEvent->DefaultPrevented() &&
3999               aVisitor.mEvent->IsTrusted() && IsMutable() && wheelEvent &&
4000               wheelEvent->mDeltaY != 0 &&
4001               wheelEvent->mDeltaMode != WheelEvent_Binding::DOM_DELTA_PIXEL) {
4002             if (mType == FormControlType::InputNumber) {
4003               if (nsContentUtils::IsFocusedContent(this)) {
4004                 StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
4005                 FireChangeEventIfNeeded();
4006                 aVisitor.mEvent->PreventDefault();
4007               }
4008             } else if (mType == FormControlType::InputRange &&
4009                        nsContentUtils::IsFocusedContent(this) &&
4010                        GetMinimum() < GetMaximum()) {
4011               Decimal value = GetValueAsDecimal();
4012               Decimal step = GetStep();
4013               if (step == kStepAny) {
4014                 step = GetDefaultStep();
4015               }
4016               MOZ_ASSERT(value.isFinite() && step.isFinite());
4017               SetValueOfRangeForUserEvent(
4018                   wheelEvent->mDeltaY < 0 ? value + step : value - step);
4019               FireChangeEventIfNeeded();
4020               aVisitor.mEvent->PreventDefault();
4021             }
4022           }
4023           break;
4024         }
4025 #endif
4026         case eMouseClick: {
4027           if (!aVisitor.mEvent->DefaultPrevented() &&
4028               aVisitor.mEvent->IsTrusted() &&
4029               mType == FormControlType::InputSearch &&
4030               aVisitor.mEvent->AsMouseEvent()->mButton ==
4031                   MouseButton::ePrimary) {
4032             if (nsSearchControlFrame* searchControlFrame =
4033                     do_QueryFrame(GetPrimaryFrame())) {
4034               Element* clearButton = searchControlFrame->GetAnonClearButton();
4035               if (clearButton &&
4036                   aVisitor.mEvent->mOriginalTarget == clearButton) {
4037                 SetUserInput(EmptyString(),
4038                              *nsContentUtils::GetSystemPrincipal());
4039               }
4040             }
4041           }
4042           break;
4043         }
4044         default:
4045           break;
4046       }
4047 
4048       if (outerActivateEvent) {
4049         if (mForm && (oldType == FormControlType::InputSubmit ||
4050                       oldType == FormControlType::InputImage)) {
4051           if (mType != FormControlType::InputSubmit &&
4052               mType != FormControlType::InputImage &&
4053               aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) {
4054             // If the type has changed to a non-submit type, then we want to
4055             // flush the stored submission if there is one (as if the submit()
4056             // was allowed to succeed)
4057             mForm->FlushPendingSubmission();
4058           }
4059         }
4060         switch (mType) {
4061           case FormControlType::InputReset:
4062           case FormControlType::InputSubmit:
4063           case FormControlType::InputImage:
4064             if (mForm) {
4065               // Hold a strong ref while dispatching
4066               RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
4067               if (mType == FormControlType::InputReset) {
4068                 form->MaybeReset(this);
4069               } else {
4070                 form->MaybeSubmit(this);
4071               }
4072               aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4073             }
4074             break;
4075 
4076           default:
4077             break;
4078         }  // switch
4079       }    // click or outer activate event
4080     } else if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) &&
4081                (oldType == FormControlType::InputSubmit ||
4082                 oldType == FormControlType::InputImage) &&
4083                mForm) {
4084       // tell the form to flush a possible pending submission.
4085       // the reason is that the script returned false (the event was
4086       // not ignored) so if there is a stored submission, it needs to
4087       // be submitted immediately.
4088       mForm->FlushPendingSubmission();
4089     }
4090   }  // if
4091 
4092   if (NS_SUCCEEDED(rv) && mType == FormControlType::InputRange) {
4093     PostHandleEventForRangeThumb(aVisitor);
4094   }
4095 
4096   return MaybeInitPickers(aVisitor);
4097 }
4098 
PostHandleEventForRangeThumb(EventChainPostVisitor & aVisitor)4099 void HTMLInputElement::PostHandleEventForRangeThumb(
4100     EventChainPostVisitor& aVisitor) {
4101   MOZ_ASSERT(mType == FormControlType::InputRange);
4102 
4103   if (nsEventStatus_eConsumeNoDefault == aVisitor.mEventStatus ||
4104       !(aVisitor.mEvent->mClass == eMouseEventClass ||
4105         aVisitor.mEvent->mClass == eTouchEventClass ||
4106         aVisitor.mEvent->mClass == eKeyboardEventClass)) {
4107     return;
4108   }
4109 
4110   nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4111   if (!rangeFrame && mIsDraggingRange) {
4112     CancelRangeThumbDrag();
4113     return;
4114   }
4115 
4116   switch (aVisitor.mEvent->mMessage) {
4117     case eMouseDown:
4118     case eTouchStart: {
4119       if (mIsDraggingRange) {
4120         break;
4121       }
4122       if (PresShell::GetCapturingContent()) {
4123         break;  // don't start drag if someone else is already capturing
4124       }
4125       WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
4126       if (IgnoreInputEventWithModifier(*inputEvent, true)) {
4127         break;  // ignore
4128       }
4129       if (aVisitor.mEvent->mMessage == eMouseDown) {
4130         if (aVisitor.mEvent->AsMouseEvent()->mButtons ==
4131             MouseButtonsFlag::ePrimaryFlag) {
4132           StartRangeThumbDrag(inputEvent);
4133         } else if (mIsDraggingRange) {
4134           CancelRangeThumbDrag();
4135         }
4136       } else {
4137         if (aVisitor.mEvent->AsTouchEvent()->mTouches.Length() == 1) {
4138           StartRangeThumbDrag(inputEvent);
4139         } else if (mIsDraggingRange) {
4140           CancelRangeThumbDrag();
4141         }
4142       }
4143       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4144     } break;
4145 
4146     case eMouseMove:
4147     case eTouchMove:
4148       if (!mIsDraggingRange) {
4149         break;
4150       }
4151       if (PresShell::GetCapturingContent() != this) {
4152         // Someone else grabbed capture.
4153         CancelRangeThumbDrag();
4154         break;
4155       }
4156       SetValueOfRangeForUserEvent(
4157           rangeFrame->GetValueAtEventPoint(aVisitor.mEvent->AsInputEvent()));
4158       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4159       break;
4160 
4161     case eMouseUp:
4162     case eTouchEnd:
4163       if (!mIsDraggingRange) {
4164         break;
4165       }
4166       // We don't check to see whether we are the capturing content here and
4167       // call CancelRangeThumbDrag() if that is the case. We just finish off
4168       // the drag and set our final value (unless someone has called
4169       // preventDefault() and prevents us getting here).
4170       FinishRangeThumbDrag(aVisitor.mEvent->AsInputEvent());
4171       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4172       break;
4173 
4174     case eKeyPress:
4175       if (mIsDraggingRange &&
4176           aVisitor.mEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
4177         CancelRangeThumbDrag();
4178       }
4179       break;
4180 
4181     case eTouchCancel:
4182       if (mIsDraggingRange) {
4183         CancelRangeThumbDrag();
4184       }
4185       break;
4186 
4187     default:
4188       break;
4189   }
4190 }
4191 
MaybeLoadImage()4192 void HTMLInputElement::MaybeLoadImage() {
4193   // Our base URI may have changed; claim that our URI changed, and the
4194   // nsImageLoadingContent will decide whether a new image load is warranted.
4195   nsAutoString uri;
4196   if (mType == FormControlType::InputImage &&
4197       GetAttr(kNameSpaceID_None, nsGkAtoms::src, uri) &&
4198       (NS_FAILED(LoadImage(uri, false, true, eImageLoadType_Normal,
4199                            mSrcTriggeringPrincipal)) ||
4200        !LoadingEnabled())) {
4201     CancelImageRequests(true);
4202   }
4203 }
4204 
BindToTree(BindContext & aContext,nsINode & aParent)4205 nsresult HTMLInputElement::BindToTree(BindContext& aContext, nsINode& aParent) {
4206   nsresult rv =
4207       nsGenericHTMLFormElementWithState::BindToTree(aContext, aParent);
4208   NS_ENSURE_SUCCESS(rv, rv);
4209 
4210   nsImageLoadingContent::BindToTree(aContext, aParent);
4211 
4212   if (mType == FormControlType::InputImage) {
4213     // Our base URI may have changed; claim that our URI changed, and the
4214     // nsImageLoadingContent will decide whether a new image load is warranted.
4215     if (HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
4216       // Mark channel as urgent-start before load image if the image load is
4217       // initaiated by a user interaction.
4218       mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
4219 
4220       nsContentUtils::AddScriptRunner(
4221           NewRunnableMethod("dom::HTMLInputElement::MaybeLoadImage", this,
4222                             &HTMLInputElement::MaybeLoadImage));
4223     }
4224   }
4225 
4226   // Add radio to document if we don't have a form already (if we do it's
4227   // already been added into that group)
4228   if (!mForm && mType == FormControlType::InputRadio &&
4229       GetUncomposedDocOrConnectedShadowRoot()) {
4230     AddedToRadioGroup();
4231   }
4232 
4233   // Set direction based on value if dir=auto
4234   if (HasDirAuto()) {
4235     SetDirectionFromValue(false);
4236   }
4237 
4238   // An element can't suffer from value missing if it is not in a document.
4239   // We have to check if we suffer from that as we are now in a document.
4240   UpdateValueMissingValidityState();
4241 
4242   // If there is a disabled fieldset in the parent chain, the element is now
4243   // barred from constraint validation and can't suffer from value missing
4244   // (call done before).
4245   UpdateBarredFromConstraintValidation();
4246 
4247   // And now make sure our state is up to date
4248   UpdateState(false);
4249 
4250   if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4251     // Construct Shadow Root so web content can be hidden in the DOM.
4252     AttachAndSetUAShadowRoot();
4253   }
4254 
4255   if (mType == FormControlType::InputPassword) {
4256     if (IsInComposedDoc()) {
4257       AsyncEventDispatcher* dispatcher =
4258           new AsyncEventDispatcher(this, u"DOMInputPasswordAdded"_ns,
4259                                    CanBubble::eYes, ChromeOnlyDispatch::eYes);
4260       dispatcher->PostDOMEvent();
4261     }
4262 
4263 #ifdef EARLY_BETA_OR_EARLIER
4264     Telemetry::Accumulate(Telemetry::PWMGR_PASSWORD_INPUT_IN_FORM, !!mForm);
4265 #endif
4266   }
4267 
4268   return rv;
4269 }
4270 
UnbindFromTree(bool aNullParent)4271 void HTMLInputElement::UnbindFromTree(bool aNullParent) {
4272   if (mType == FormControlType::InputPassword) {
4273     MaybeFireInputPasswordRemoved();
4274   }
4275 
4276   // If we have a form and are unbound from it,
4277   // nsGenericHTMLFormElementWithState::UnbindFromTree() will unset the form and
4278   // that takes care of form's WillRemove so we just have to take care
4279   // of the case where we're removing from the document and we don't
4280   // have a form
4281   if (!mForm && mType == FormControlType::InputRadio) {
4282     WillRemoveFromRadioGroup();
4283   }
4284 
4285   if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4286     NotifyUAWidgetTeardown();
4287   }
4288 
4289   nsImageLoadingContent::UnbindFromTree(aNullParent);
4290   nsGenericHTMLFormElementWithState::UnbindFromTree(aNullParent);
4291 
4292   // GetCurrentDoc is returning nullptr so we can update the value
4293   // missing validity state to reflect we are no longer into a doc.
4294   UpdateValueMissingValidityState();
4295   // We might be no longer disabled because of parent chain changed.
4296   UpdateBarredFromConstraintValidation();
4297 
4298   // And now make sure our state is up to date
4299   UpdateState(false);
4300 }
4301 
4302 namespace {
4303 class TypeChangeSelectionRangeFlagDeterminer {
4304  public:
4305   using ValueSetterOption = TextControlState::ValueSetterOption;
4306   using ValueSetterOptions = TextControlState::ValueSetterOptions;
4307 
4308   // @param aOldType InputElementTypes
4309   // @param aNewType InputElementTypes
TypeChangeSelectionRangeFlagDeterminer(FormControlType aOldType,FormControlType aNewType)4310   TypeChangeSelectionRangeFlagDeterminer(FormControlType aOldType,
4311                                          FormControlType aNewType)
4312       : mOldType(aOldType), mNewType(aNewType) {}
4313 
4314   // @return TextControlState::ValueSetterOptions
GetValueSetterOptions() const4315   ValueSetterOptions GetValueSetterOptions() const {
4316     const bool previouslySelectable = DoesSetRangeTextApply(mOldType);
4317     const bool nowSelectable = DoesSetRangeTextApply(mNewType);
4318     const bool moveCursorToBeginAndSetDirectionForward =
4319         !previouslySelectable && nowSelectable;
4320     if (moveCursorToBeginAndSetDirectionForward) {
4321       return {ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward};
4322     }
4323     return {};
4324   }
4325 
4326  private:
4327   /**
4328    * @param aType InputElementTypes
4329    * @return true, iff SetRangeText applies to aType as specified at
4330    * https://html.spec.whatwg.org/multipage/input.html#concept-input-apply.
4331    */
DoesSetRangeTextApply(FormControlType aType)4332   static bool DoesSetRangeTextApply(FormControlType aType) {
4333     return aType == FormControlType::InputText ||
4334            aType == FormControlType::InputSearch ||
4335            aType == FormControlType::InputUrl ||
4336            aType == FormControlType::InputTel ||
4337            aType == FormControlType::InputPassword;
4338   }
4339 
4340   const FormControlType mOldType;
4341   const FormControlType mNewType;
4342 };
4343 }  // anonymous namespace
4344 
HandleTypeChange(FormControlType aNewType,bool aNotify)4345 void HTMLInputElement::HandleTypeChange(FormControlType aNewType,
4346                                         bool aNotify) {
4347   FormControlType oldType = mType;
4348   MOZ_ASSERT(oldType != aNewType);
4349 
4350   mHasBeenTypePassword =
4351       mHasBeenTypePassword || aNewType == FormControlType::InputPassword;
4352 
4353   if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
4354     // Input element can represent very different kinds of UIs, and we may
4355     // need to flush styling even when focusing the already focused input
4356     // element.
4357     fm->NeedsFlushBeforeEventHandling(this);
4358   }
4359 
4360   if (aNewType == FormControlType::InputFile ||
4361       oldType == FormControlType::InputFile) {
4362     if (aNewType == FormControlType::InputFile) {
4363       mFileData.reset(new FileData());
4364     } else {
4365       mFileData->Unlink();
4366       mFileData = nullptr;
4367     }
4368   }
4369 
4370   if (oldType == FormControlType::InputRange && mIsDraggingRange) {
4371     CancelRangeThumbDrag(false);
4372   }
4373 
4374   ValueModeType aOldValueMode = GetValueMode();
4375   nsAutoString aOldValue;
4376 
4377   if (aOldValueMode == VALUE_MODE_VALUE) {
4378     // Doesn't matter what caller type we pass here, since we know we're not a
4379     // file input anyway.
4380     GetValue(aOldValue, CallerType::NonSystem);
4381   }
4382 
4383   TextControlState::SelectionProperties sp;
4384 
4385   if (GetEditorState()) {
4386     mInputData.mState->SyncUpSelectionPropertiesBeforeDestruction();
4387     sp = mInputData.mState->GetSelectionProperties();
4388   }
4389 
4390   // We already have a copy of the value, lets free it and changes the type.
4391   FreeData();
4392   mType = aNewType;
4393   void* memory = mInputTypeMem;
4394   mInputType = InputType::Create(this, mType, memory);
4395 
4396   if (IsSingleLineTextControl()) {
4397     mInputData.mState = TextControlState::Construct(this);
4398     if (!sp.IsDefault()) {
4399       mInputData.mState->SetSelectionProperties(sp);
4400     }
4401   }
4402 
4403   /**
4404    * The following code is trying to reproduce the algorithm described here:
4405    * http://www.whatwg.org/specs/web-apps/current-work/complete.html#input-type-change
4406    */
4407   switch (GetValueMode()) {
4408     case VALUE_MODE_DEFAULT:
4409     case VALUE_MODE_DEFAULT_ON:
4410       // If the previous value mode was value, we need to set the value content
4411       // attribute to the previous value.
4412       // There is no value sanitizing algorithm for elements in this mode.
4413       if (aOldValueMode == VALUE_MODE_VALUE && !aOldValue.IsEmpty()) {
4414         SetAttr(kNameSpaceID_None, nsGkAtoms::value, aOldValue, true);
4415       }
4416       break;
4417     case VALUE_MODE_VALUE:
4418       // If the previous value mode wasn't value, we have to set the value to
4419       // the value content attribute.
4420       // SetValueInternal is going to sanitize the value.
4421       {
4422         nsAutoString value;
4423         if (aOldValueMode != VALUE_MODE_VALUE) {
4424           GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
4425         } else {
4426           value = aOldValue;
4427         }
4428 
4429         const TypeChangeSelectionRangeFlagDeterminer flagDeterminer(oldType,
4430                                                                     mType);
4431         ValueSetterOptions options(flagDeterminer.GetValueSetterOptions());
4432         options += ValueSetterOption::ByInternalAPI;
4433 
4434         // TODO: What should we do if SetValueInternal fails?  (The allocation
4435         // may potentially be big, but most likely we've failed to allocate
4436         // before the type change.)
4437         SetValueInternal(value, options);
4438       }
4439       break;
4440     case VALUE_MODE_FILENAME:
4441     default:
4442       // We don't care about the value.
4443       // There is no value sanitizing algorithm for elements in this mode.
4444       break;
4445   }
4446 
4447   // Updating mFocusedValue in consequence:
4448   // If the new type fires a change event on blur, but the previous type
4449   // doesn't, we should set mFocusedValue to the current value.
4450   // Otherwise, if the new type doesn't fire a change event on blur, but the
4451   // previous type does, we should clear out mFocusedValue.
4452   if (MayFireChangeOnBlur(mType) && !MayFireChangeOnBlur(oldType)) {
4453     GetValue(mFocusedValue, CallerType::System);
4454   } else if (!IsSingleLineTextControl(false, mType) &&
4455              IsSingleLineTextControl(false, oldType)) {
4456     mFocusedValue.Truncate();
4457   }
4458 
4459   // Update or clear our required states since we may have changed from a
4460   // required input type to a non-required input type or viceversa.
4461   if (DoesRequiredApply()) {
4462     bool isRequired = HasAttr(kNameSpaceID_None, nsGkAtoms::required);
4463     UpdateRequiredState(isRequired, aNotify);
4464   } else if (aNotify) {
4465     RemoveStates(REQUIRED_STATES);
4466   } else {
4467     RemoveStatesSilently(REQUIRED_STATES);
4468   }
4469 
4470   UpdateHasRange();
4471 
4472   // Update validity states, but not element state.  We'll update
4473   // element state later, as part of this attribute change.
4474   UpdateAllValidityStatesButNotElementState();
4475 
4476   UpdateApzAwareFlag();
4477 
4478   UpdateBarredFromConstraintValidation();
4479 
4480   if (oldType == FormControlType::InputImage) {
4481     // We're no longer an image input.  Cancel our image requests, if we have
4482     // any.
4483     CancelImageRequests(aNotify);
4484 
4485     // And we should update our mapped attribute mapping function.
4486     mAttrs.UpdateMappedAttrRuleMapper(*this);
4487   } else if (mType == FormControlType::InputImage) {
4488     if (aNotify) {
4489       // We just got switched to be an image input; we should see
4490       // whether we have an image to load;
4491       nsAutoString src;
4492       if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
4493         // Mark channel as urgent-start before load image if the image load is
4494         // initaiated by a user interaction.
4495         mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
4496 
4497         LoadImage(src, false, aNotify, eImageLoadType_Normal,
4498                   mSrcTriggeringPrincipal);
4499       }
4500     }
4501 
4502     // And we should update our mapped attribute mapping function.
4503     mAttrs.UpdateMappedAttrRuleMapper(*this);
4504   }
4505 
4506   if (mType == FormControlType::InputPassword && IsInComposedDoc()) {
4507     AsyncEventDispatcher* dispatcher =
4508         new AsyncEventDispatcher(this, u"DOMInputPasswordAdded"_ns,
4509                                  CanBubble::eYes, ChromeOnlyDispatch::eYes);
4510     dispatcher->PostDOMEvent();
4511   }
4512 
4513   if (IsInComposedDoc()) {
4514     if (CreatesDateTimeWidget(oldType)) {
4515       if (!CreatesDateTimeWidget()) {
4516         // Switch away from date/time type.
4517         NotifyUAWidgetTeardown();
4518       } else {
4519         // Switch between date and time.
4520         NotifyUAWidgetSetupOrChange();
4521       }
4522     } else if (CreatesDateTimeWidget()) {
4523       // Switch to date/time type.
4524       AttachAndSetUAShadowRoot();
4525     }
4526   }
4527 }
4528 
SanitizeValue(nsAString & aValue,ForValueGetter aForGetter)4529 void HTMLInputElement::SanitizeValue(nsAString& aValue,
4530                                      ForValueGetter aForGetter) {
4531   NS_ASSERTION(mDoneCreating, "The element creation should be finished!");
4532 
4533   switch (mType) {
4534     case FormControlType::InputText:
4535     case FormControlType::InputSearch:
4536     case FormControlType::InputTel:
4537     case FormControlType::InputPassword: {
4538       aValue.StripCRLF();
4539     } break;
4540     case FormControlType::InputEmail:
4541     case FormControlType::InputUrl: {
4542       aValue.StripCRLF();
4543 
4544       aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
4545           aValue);
4546     } break;
4547     case FormControlType::InputNumber: {
4548       Decimal value;
4549       bool ok = mInputType->ConvertStringToNumber(aValue, value);
4550       if (!ok) {
4551         aValue.Truncate();
4552         return;
4553       }
4554 
4555       nsAutoString sanitizedValue;
4556       if (aForGetter == ForValueGetter::Yes) {
4557         // If the default non-localized algorithm parses the value, then we're
4558         // done, don't un-localize it, to avoid precision loss, and to preserve
4559         // scientific notation as well for example.
4560         //
4561         // FIXME(emilio, bug 1622808): Localization should ideally be more
4562         // input-preserving.
4563         if (StringToDecimal(aValue).isFinite()) {
4564           return;
4565         }
4566         // For the <input type=number> value getter, we return the unlocalized
4567         // value if it doesn't parse as StringToDecimal, for compat with other
4568         // browsers.
4569         char buf[32];
4570         DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
4571         sanitizedValue.AssignASCII(buf);
4572         MOZ_ASSERT(ok, "buf not big enough");
4573       } else {
4574         mInputType->ConvertNumberToString(value, sanitizedValue);
4575         // Otherwise we localize as needed, but if both the localized and
4576         // unlocalized version parse, we just use the unlocalized one, to
4577         // preserve the input as much as possible.
4578         //
4579         // FIXME(emilio, bug 1622808): Localization should ideally be more
4580         // input-preserving.
4581         if (StringToDecimal(sanitizedValue).isFinite()) {
4582           return;
4583         }
4584       }
4585       aValue.Assign(sanitizedValue);
4586     } break;
4587     case FormControlType::InputRange: {
4588       Decimal minimum = GetMinimum();
4589       Decimal maximum = GetMaximum();
4590       MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
4591                  "type=range should have a default maximum/minimum");
4592 
4593       // We use this to avoid modifying the string unnecessarily, since that
4594       // may introduce rounding. This is set to true only if the value we
4595       // parse out from aValue needs to be sanitized.
4596       bool needSanitization = false;
4597 
4598       Decimal value;
4599       bool ok = mInputType->ConvertStringToNumber(aValue, value);
4600       if (!ok) {
4601         needSanitization = true;
4602         // Set value to midway between minimum and maximum.
4603         value = maximum <= minimum ? minimum
4604                                    : minimum + (maximum - minimum) / Decimal(2);
4605       } else if (value < minimum || maximum < minimum) {
4606         needSanitization = true;
4607         value = minimum;
4608       } else if (value > maximum) {
4609         needSanitization = true;
4610         value = maximum;
4611       }
4612 
4613       Decimal step = GetStep();
4614       if (step != kStepAny) {
4615         Decimal stepBase = GetStepBase();
4616         // There could be rounding issues below when dealing with fractional
4617         // numbers, but let's ignore that until ECMAScript supplies us with a
4618         // decimal number type.
4619         Decimal deltaToStep = NS_floorModulo(value - stepBase, step);
4620         if (deltaToStep != Decimal(0)) {
4621           // "suffering from a step mismatch"
4622           // Round the element's value to the nearest number for which the
4623           // element would not suffer from a step mismatch, and which is
4624           // greater than or equal to the minimum, and, if the maximum is not
4625           // less than the minimum, which is less than or equal to the
4626           // maximum, if there is a number that matches these constraints:
4627           MOZ_ASSERT(deltaToStep > Decimal(0),
4628                      "stepBelow/stepAbove will be wrong");
4629           Decimal stepBelow = value - deltaToStep;
4630           Decimal stepAbove = value - deltaToStep + step;
4631           Decimal halfStep = step / Decimal(2);
4632           bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
4633           bool stepAboveInRange = stepAbove >= minimum && stepAbove <= maximum;
4634           bool stepBelowInRange = stepBelow >= minimum && stepBelow <= maximum;
4635 
4636           if ((stepAboveIsClosest || !stepBelowInRange) && stepAboveInRange) {
4637             needSanitization = true;
4638             value = stepAbove;
4639           } else if ((!stepAboveIsClosest || !stepAboveInRange) &&
4640                      stepBelowInRange) {
4641             needSanitization = true;
4642             value = stepBelow;
4643           }
4644         }
4645       }
4646 
4647       if (needSanitization) {
4648         char buf[32];
4649         DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
4650         aValue.AssignASCII(buf);
4651         MOZ_ASSERT(ok, "buf not big enough");
4652       }
4653     } break;
4654     case FormControlType::InputDate: {
4655       if (!aValue.IsEmpty() && !IsValidDate(aValue)) {
4656         aValue.Truncate();
4657       }
4658     } break;
4659     case FormControlType::InputTime: {
4660       if (!aValue.IsEmpty() && !IsValidTime(aValue)) {
4661         aValue.Truncate();
4662       }
4663     } break;
4664     case FormControlType::InputMonth: {
4665       if (!aValue.IsEmpty() && !IsValidMonth(aValue)) {
4666         aValue.Truncate();
4667       }
4668     } break;
4669     case FormControlType::InputWeek: {
4670       if (!aValue.IsEmpty() && !IsValidWeek(aValue)) {
4671         aValue.Truncate();
4672       }
4673     } break;
4674     case FormControlType::InputDatetimeLocal: {
4675       if (!aValue.IsEmpty() && !IsValidDateTimeLocal(aValue)) {
4676         aValue.Truncate();
4677       } else {
4678         NormalizeDateTimeLocal(aValue);
4679       }
4680     } break;
4681     case FormControlType::InputColor: {
4682       if (IsValidSimpleColor(aValue)) {
4683         ToLowerCase(aValue);
4684       } else {
4685         // Set default (black) color, if aValue wasn't parsed correctly.
4686         aValue.AssignLiteral("#000000");
4687       }
4688     } break;
4689     default:
4690       break;
4691   }
4692 }
4693 
IsValidSimpleColor(const nsAString & aValue) const4694 bool HTMLInputElement::IsValidSimpleColor(const nsAString& aValue) const {
4695   if (aValue.Length() != 7 || aValue.First() != '#') {
4696     return false;
4697   }
4698 
4699   for (int i = 1; i < 7; ++i) {
4700     if (!IsAsciiDigit(aValue[i]) && !(aValue[i] >= 'a' && aValue[i] <= 'f') &&
4701         !(aValue[i] >= 'A' && aValue[i] <= 'F')) {
4702       return false;
4703     }
4704   }
4705   return true;
4706 }
4707 
IsLeapYear(uint32_t aYear) const4708 bool HTMLInputElement::IsLeapYear(uint32_t aYear) const {
4709   if ((aYear % 4 == 0 && aYear % 100 != 0) || (aYear % 400 == 0)) {
4710     return true;
4711   }
4712   return false;
4713 }
4714 
DayOfWeek(uint32_t aYear,uint32_t aMonth,uint32_t aDay,bool isoWeek) const4715 uint32_t HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth,
4716                                      uint32_t aDay, bool isoWeek) const {
4717   // Tomohiko Sakamoto algorithm.
4718   int monthTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
4719   aYear -= aMonth < 3;
4720 
4721   uint32_t day = (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
4722                   monthTable[aMonth - 1] + aDay) %
4723                  7;
4724 
4725   if (isoWeek) {
4726     return ((day + 6) % 7) + 1;
4727   }
4728 
4729   return day;
4730 }
4731 
MaximumWeekInYear(uint32_t aYear) const4732 uint32_t HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const {
4733   int day = DayOfWeek(aYear, 1, 1, true);  // January 1.
4734   // A year starting on Thursday or a leap year starting on Wednesday has 53
4735   // weeks. All other years have 52 weeks.
4736   return day == 4 || (day == 3 && IsLeapYear(aYear)) ? kMaximumWeekInYear
4737                                                      : kMaximumWeekInYear - 1;
4738 }
4739 
IsValidWeek(const nsAString & aValue) const4740 bool HTMLInputElement::IsValidWeek(const nsAString& aValue) const {
4741   uint32_t year, week;
4742   return ParseWeek(aValue, &year, &week);
4743 }
4744 
IsValidMonth(const nsAString & aValue) const4745 bool HTMLInputElement::IsValidMonth(const nsAString& aValue) const {
4746   uint32_t year, month;
4747   return ParseMonth(aValue, &year, &month);
4748 }
4749 
IsValidDate(const nsAString & aValue) const4750 bool HTMLInputElement::IsValidDate(const nsAString& aValue) const {
4751   uint32_t year, month, day;
4752   return ParseDate(aValue, &year, &month, &day);
4753 }
4754 
IsValidDateTimeLocal(const nsAString & aValue) const4755 bool HTMLInputElement::IsValidDateTimeLocal(const nsAString& aValue) const {
4756   uint32_t year, month, day, time;
4757   return ParseDateTimeLocal(aValue, &year, &month, &day, &time);
4758 }
4759 
ParseYear(const nsAString & aValue,uint32_t * aYear) const4760 bool HTMLInputElement::ParseYear(const nsAString& aValue,
4761                                  uint32_t* aYear) const {
4762   if (aValue.Length() < 4) {
4763     return false;
4764   }
4765 
4766   return DigitSubStringToNumber(aValue, 0, aValue.Length(), aYear) &&
4767          *aYear > 0;
4768 }
4769 
ParseMonth(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth) const4770 bool HTMLInputElement::ParseMonth(const nsAString& aValue, uint32_t* aYear,
4771                                   uint32_t* aMonth) const {
4772   // Parse the year, month values out a string formatted as 'yyyy-mm'.
4773   if (aValue.Length() < 7) {
4774     return false;
4775   }
4776 
4777   uint32_t endOfYearOffset = aValue.Length() - 3;
4778   if (aValue[endOfYearOffset] != '-') {
4779     return false;
4780   }
4781 
4782   const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
4783   if (!ParseYear(yearStr, aYear)) {
4784     return false;
4785   }
4786 
4787   return DigitSubStringToNumber(aValue, endOfYearOffset + 1, 2, aMonth) &&
4788          *aMonth > 0 && *aMonth <= 12;
4789 }
4790 
ParseWeek(const nsAString & aValue,uint32_t * aYear,uint32_t * aWeek) const4791 bool HTMLInputElement::ParseWeek(const nsAString& aValue, uint32_t* aYear,
4792                                  uint32_t* aWeek) const {
4793   // Parse the year, month values out a string formatted as 'yyyy-Www'.
4794   if (aValue.Length() < 8) {
4795     return false;
4796   }
4797 
4798   uint32_t endOfYearOffset = aValue.Length() - 4;
4799   if (aValue[endOfYearOffset] != '-') {
4800     return false;
4801   }
4802 
4803   if (aValue[endOfYearOffset + 1] != 'W') {
4804     return false;
4805   }
4806 
4807   const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
4808   if (!ParseYear(yearStr, aYear)) {
4809     return false;
4810   }
4811 
4812   return DigitSubStringToNumber(aValue, endOfYearOffset + 2, 2, aWeek) &&
4813          *aWeek > 0 && *aWeek <= MaximumWeekInYear(*aYear);
4814 }
4815 
ParseDate(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth,uint32_t * aDay) const4816 bool HTMLInputElement::ParseDate(const nsAString& aValue, uint32_t* aYear,
4817                                  uint32_t* aMonth, uint32_t* aDay) const {
4818   /*
4819    * Parse the year, month, day values out a date string formatted as
4820    * yyyy-mm-dd. -The year must be 4 or more digits long, and year > 0 -The
4821    * month must be exactly 2 digits long, and 01 <= month <= 12 -The day must be
4822    * exactly 2 digit long, and 01 <= day <= maxday Where maxday is the number of
4823    * days in the month 'month' and year 'year'
4824    */
4825   if (aValue.Length() < 10) {
4826     return false;
4827   }
4828 
4829   uint32_t endOfMonthOffset = aValue.Length() - 3;
4830   if (aValue[endOfMonthOffset] != '-') {
4831     return false;
4832   }
4833 
4834   const nsAString& yearMonthStr = Substring(aValue, 0, endOfMonthOffset);
4835   if (!ParseMonth(yearMonthStr, aYear, aMonth)) {
4836     return false;
4837   }
4838 
4839   return DigitSubStringToNumber(aValue, endOfMonthOffset + 1, 2, aDay) &&
4840          *aDay > 0 && *aDay <= NumberOfDaysInMonth(*aMonth, *aYear);
4841 }
4842 
ParseDateTimeLocal(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth,uint32_t * aDay,uint32_t * aTime) const4843 bool HTMLInputElement::ParseDateTimeLocal(const nsAString& aValue,
4844                                           uint32_t* aYear, uint32_t* aMonth,
4845                                           uint32_t* aDay,
4846                                           uint32_t* aTime) const {
4847   // Parse the year, month, day and time values out a string formatted as
4848   // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
4849   // seconds can be 1 to 3 digits.
4850   // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
4851   // or 'yyyy-mm-dd hh:mm'.
4852   if (aValue.Length() < 16) {
4853     return false;
4854   }
4855 
4856   int32_t sepIndex = aValue.FindChar('T');
4857   if (sepIndex == -1) {
4858     sepIndex = aValue.FindChar(' ');
4859 
4860     if (sepIndex == -1) {
4861       return false;
4862     }
4863   }
4864 
4865   const nsAString& dateStr = Substring(aValue, 0, sepIndex);
4866   if (!ParseDate(dateStr, aYear, aMonth, aDay)) {
4867     return false;
4868   }
4869 
4870   const nsAString& timeStr =
4871       Substring(aValue, sepIndex + 1, aValue.Length() - sepIndex + 1);
4872   if (!ParseTime(timeStr, aTime)) {
4873     return false;
4874   }
4875 
4876   return true;
4877 }
4878 
NormalizeDateTimeLocal(nsAString & aValue) const4879 void HTMLInputElement::NormalizeDateTimeLocal(nsAString& aValue) const {
4880   if (aValue.IsEmpty()) {
4881     return;
4882   }
4883 
4884   // Use 'T' as the separator between date string and time string.
4885   int32_t sepIndex = aValue.FindChar(' ');
4886   if (sepIndex != -1) {
4887     aValue.ReplaceLiteral(sepIndex, 1, u"T");
4888   } else {
4889     sepIndex = aValue.FindChar('T');
4890   }
4891 
4892   // Time expressed as the shortest possible string, which is hh:mm.
4893   if ((aValue.Length() - sepIndex) == 6) {
4894     return;
4895   }
4896 
4897   // Fractions of seconds part is optional, ommit it if it's 0.
4898   if ((aValue.Length() - sepIndex) > 9) {
4899     const uint32_t millisecSepIndex = sepIndex + 9;
4900     uint32_t milliseconds;
4901     if (!DigitSubStringToNumber(aValue, millisecSepIndex + 1,
4902                                 aValue.Length() - (millisecSepIndex + 1),
4903                                 &milliseconds)) {
4904       return;
4905     }
4906 
4907     if (milliseconds != 0) {
4908       return;
4909     }
4910 
4911     aValue.Cut(millisecSepIndex, aValue.Length() - millisecSepIndex);
4912   }
4913 
4914   // Seconds part is optional, ommit it if it's 0.
4915   const uint32_t secondSepIndex = sepIndex + 6;
4916   uint32_t seconds;
4917   if (!DigitSubStringToNumber(aValue, secondSepIndex + 1,
4918                               aValue.Length() - (secondSepIndex + 1),
4919                               &seconds)) {
4920     return;
4921   }
4922 
4923   if (seconds != 0) {
4924     return;
4925   }
4926 
4927   aValue.Cut(secondSepIndex, aValue.Length() - secondSepIndex);
4928 }
4929 
DaysSinceEpochFromWeek(uint32_t aYear,uint32_t aWeek) const4930 double HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear,
4931                                                 uint32_t aWeek) const {
4932   double days = JS::DayFromYear(aYear) + (aWeek - 1) * 7;
4933   uint32_t dayOneIsoWeekday = DayOfWeek(aYear, 1, 1, true);
4934 
4935   // If day one of that year is on/before Thursday, we should subtract the
4936   // days that belong to last year in our first week, otherwise, our first
4937   // days belong to last year's last week, and we should add those days
4938   // back.
4939   if (dayOneIsoWeekday <= 4) {
4940     days -= (dayOneIsoWeekday - 1);
4941   } else {
4942     days += (7 - dayOneIsoWeekday + 1);
4943   }
4944 
4945   return days;
4946 }
4947 
NumberOfDaysInMonth(uint32_t aMonth,uint32_t aYear) const4948 uint32_t HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth,
4949                                                uint32_t aYear) const {
4950   /*
4951    * Returns the number of days in a month.
4952    * Months that are |longMonths| always have 31 days.
4953    * Months that are not |longMonths| have 30 days except February (month 2).
4954    * February has 29 days during leap years which are years that are divisible
4955    * by 400. or divisible by 100 and 4. February has 28 days otherwise.
4956    */
4957 
4958   static const bool longMonths[] = {true, false, true,  false, true,  false,
4959                                     true, true,  false, true,  false, true};
4960   MOZ_ASSERT(aMonth <= 12 && aMonth > 0);
4961 
4962   if (longMonths[aMonth - 1]) {
4963     return 31;
4964   }
4965 
4966   if (aMonth != 2) {
4967     return 30;
4968   }
4969 
4970   return IsLeapYear(aYear) ? 29 : 28;
4971 }
4972 
4973 /* static */
DigitSubStringToNumber(const nsAString & aStr,uint32_t aStart,uint32_t aLen,uint32_t * aRetVal)4974 bool HTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
4975                                               uint32_t aStart, uint32_t aLen,
4976                                               uint32_t* aRetVal) {
4977   MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
4978 
4979   for (uint32_t offset = 0; offset < aLen; ++offset) {
4980     if (!IsAsciiDigit(aStr[aStart + offset])) {
4981       return false;
4982     }
4983   }
4984 
4985   nsresult ec;
4986   *aRetVal = static_cast<uint32_t>(
4987       PromiseFlatString(Substring(aStr, aStart, aLen)).ToInteger(&ec));
4988 
4989   return NS_SUCCEEDED(ec);
4990 }
4991 
IsValidTime(const nsAString & aValue) const4992 bool HTMLInputElement::IsValidTime(const nsAString& aValue) const {
4993   return ParseTime(aValue, nullptr);
4994 }
4995 
4996 /* static */
ParseTime(const nsAString & aValue,uint32_t * aResult)4997 bool HTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult) {
4998   /* The string must have the following parts:
4999    * - HOURS: two digits, value being in [0, 23];
5000    * - Colon (:);
5001    * - MINUTES: two digits, value being in [0, 59];
5002    * - Optional:
5003    *   - Colon (:);
5004    *   - SECONDS: two digits, value being in [0, 59];
5005    *   - Optional:
5006    *     - DOT (.);
5007    *     - FRACTIONAL SECONDS: one to three digits, no value range.
5008    */
5009 
5010   // The following format is the shorter one allowed: "HH:MM".
5011   if (aValue.Length() < 5) {
5012     return false;
5013   }
5014 
5015   uint32_t hours;
5016   if (!DigitSubStringToNumber(aValue, 0, 2, &hours) || hours > 23) {
5017     return false;
5018   }
5019 
5020   // Hours/minutes separator.
5021   if (aValue[2] != ':') {
5022     return false;
5023   }
5024 
5025   uint32_t minutes;
5026   if (!DigitSubStringToNumber(aValue, 3, 2, &minutes) || minutes > 59) {
5027     return false;
5028   }
5029 
5030   if (aValue.Length() == 5) {
5031     if (aResult) {
5032       *aResult = ((hours * 60) + minutes) * 60000;
5033     }
5034     return true;
5035   }
5036 
5037   // The following format is the next shorter one: "HH:MM:SS".
5038   if (aValue.Length() < 8 || aValue[5] != ':') {
5039     return false;
5040   }
5041 
5042   uint32_t seconds;
5043   if (!DigitSubStringToNumber(aValue, 6, 2, &seconds) || seconds > 59) {
5044     return false;
5045   }
5046 
5047   if (aValue.Length() == 8) {
5048     if (aResult) {
5049       *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000;
5050     }
5051     return true;
5052   }
5053 
5054   // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
5055   // There can be 1 to 3 digits for the fractions of seconds.
5056   if (aValue.Length() == 9 || aValue.Length() > 12 || aValue[8] != '.') {
5057     return false;
5058   }
5059 
5060   uint32_t fractionsSeconds;
5061   if (!DigitSubStringToNumber(aValue, 9, aValue.Length() - 9,
5062                               &fractionsSeconds)) {
5063     return false;
5064   }
5065 
5066   if (aResult) {
5067     *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000 +
5068                // NOTE: there is 10.0 instead of 10 and static_cast<int> because
5069                // some old [and stupid] compilers can't just do the right thing.
5070                fractionsSeconds *
5071                    pow(10.0, static_cast<int>(3 - (aValue.Length() - 9)));
5072   }
5073 
5074   return true;
5075 }
5076 
5077 /* static */
IsDateTimeTypeSupported(FormControlType aDateTimeInputType)5078 bool HTMLInputElement::IsDateTimeTypeSupported(
5079     FormControlType aDateTimeInputType) {
5080   switch (aDateTimeInputType) {
5081     case FormControlType::InputDate:
5082     case FormControlType::InputTime:
5083       return true;
5084     case FormControlType::InputDatetimeLocal:
5085       return StaticPrefs::dom_forms_datetime_local();
5086     case FormControlType::InputMonth:
5087     case FormControlType::InputWeek:
5088       return StaticPrefs::dom_forms_datetime_others();
5089     default:
5090       return false;
5091   }
5092 }
5093 
ParseAttribute(int32_t aNamespaceID,nsAtom * aAttribute,const nsAString & aValue,nsIPrincipal * aMaybeScriptedPrincipal,nsAttrValue & aResult)5094 bool HTMLInputElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
5095                                       const nsAString& aValue,
5096                                       nsIPrincipal* aMaybeScriptedPrincipal,
5097                                       nsAttrValue& aResult) {
5098   // We can't make these static_asserts because kInputDefaultType and
5099   // kInputTypeTable aren't constexpr.
5100   MOZ_ASSERT(
5101       FormControlType(kInputDefaultType->value) == FormControlType::InputText,
5102       "Someone forgot to update kInputDefaultType when adding a new "
5103       "input type.");
5104   MOZ_ASSERT(kInputTypeTable[ArrayLength(kInputTypeTable) - 1].tag == nullptr,
5105              "Last entry in the table must be the nullptr guard");
5106   MOZ_ASSERT(FormControlType(
5107                  kInputTypeTable[ArrayLength(kInputTypeTable) - 2].value) ==
5108                  FormControlType::InputText,
5109              "Next to last entry in the table must be the \"text\" entry");
5110 
5111   if (aNamespaceID == kNameSpaceID_None) {
5112     if (aAttribute == nsGkAtoms::type) {
5113       aResult.ParseEnumValue(aValue, kInputTypeTable, false, kInputDefaultType);
5114       auto newType = FormControlType(aResult.GetEnumValue());
5115       if (IsDateTimeInputType(newType) && !IsDateTimeTypeSupported(newType)) {
5116         // There's no public way to set an nsAttrValue to an enum value, but we
5117         // can just re-parse with a table that doesn't have any types other than
5118         // "text" in it.
5119         aResult.ParseEnumValue(aValue, kInputDefaultType, false,
5120                                kInputDefaultType);
5121       }
5122 
5123       return true;
5124     }
5125     if (aAttribute == nsGkAtoms::width) {
5126       return aResult.ParseHTMLDimension(aValue);
5127     }
5128     if (aAttribute == nsGkAtoms::height) {
5129       return aResult.ParseHTMLDimension(aValue);
5130     }
5131     if (aAttribute == nsGkAtoms::maxlength) {
5132       return aResult.ParseNonNegativeIntValue(aValue);
5133     }
5134     if (aAttribute == nsGkAtoms::minlength) {
5135       return aResult.ParseNonNegativeIntValue(aValue);
5136     }
5137     if (aAttribute == nsGkAtoms::size) {
5138       return aResult.ParsePositiveIntValue(aValue);
5139     }
5140     if (aAttribute == nsGkAtoms::align) {
5141       return ParseAlignValue(aValue, aResult);
5142     }
5143     if (aAttribute == nsGkAtoms::formmethod) {
5144       if (StaticPrefs::dom_dialog_element_enabled() || IsInChromeDocument()) {
5145         return aResult.ParseEnumValue(aValue, kFormMethodTableDialogEnabled,
5146                                       false);
5147       }
5148       return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
5149     }
5150     if (aAttribute == nsGkAtoms::formenctype) {
5151       return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
5152     }
5153     if (aAttribute == nsGkAtoms::autocomplete) {
5154       aResult.ParseAtomArray(aValue);
5155       return true;
5156     }
5157     if (aAttribute == nsGkAtoms::capture) {
5158       return aResult.ParseEnumValue(aValue, kCaptureTable, false,
5159                                     kCaptureDefault);
5160     }
5161     if (ParseImageAttribute(aAttribute, aValue, aResult)) {
5162       // We have to call |ParseImageAttribute| unconditionally since we
5163       // don't know if we're going to have a type="image" attribute yet,
5164       // (or could have it set dynamically in the future).  See bug
5165       // 214077.
5166       return true;
5167     }
5168   }
5169 
5170   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
5171                                               aMaybeScriptedPrincipal, aResult);
5172 }
5173 
ImageInputMapAttributesIntoRule(const nsMappedAttributes * aAttributes,MappedDeclarations & aDecls)5174 void HTMLInputElement::ImageInputMapAttributesIntoRule(
5175     const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
5176   nsGenericHTMLFormElementWithState::MapImageBorderAttributeInto(aAttributes,
5177                                                                  aDecls);
5178   nsGenericHTMLFormElementWithState::MapImageMarginAttributeInto(aAttributes,
5179                                                                  aDecls);
5180   nsGenericHTMLFormElementWithState::MapImageSizeAttributesInto(
5181       aAttributes, aDecls, MapAspectRatio::Yes);
5182   // Images treat align as "float"
5183   nsGenericHTMLFormElementWithState::MapImageAlignAttributeInto(aAttributes,
5184                                                                 aDecls);
5185 
5186   nsGenericHTMLFormElementWithState::MapCommonAttributesInto(aAttributes,
5187                                                              aDecls);
5188 }
5189 
GetAttributeChangeHint(const nsAtom * aAttribute,int32_t aModType) const5190 nsChangeHint HTMLInputElement::GetAttributeChangeHint(const nsAtom* aAttribute,
5191                                                       int32_t aModType) const {
5192   nsChangeHint retval =
5193       nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute,
5194                                                                 aModType);
5195 
5196   const bool isAdditionOrRemoval =
5197       aModType == MutationEvent_Binding::ADDITION ||
5198       aModType == MutationEvent_Binding::REMOVAL;
5199 
5200   const bool reconstruct = [&] {
5201     if (aAttribute == nsGkAtoms::type) {
5202       return true;
5203     }
5204 
5205     if (PlaceholderApplies() && aAttribute == nsGkAtoms::placeholder &&
5206         isAdditionOrRemoval) {
5207       // We need to re-create our placeholder text.
5208       return true;
5209     }
5210 
5211     if (mType == FormControlType::InputFile &&
5212         (aAttribute == nsGkAtoms::allowdirs ||
5213          aAttribute == nsGkAtoms::webkitdirectory)) {
5214       // The presence or absence of the 'directory' attribute determines what
5215       // value we show in the file label when empty, via GetDisplayFileName.
5216       return true;
5217     }
5218 
5219     if (mType == FormControlType::InputImage && isAdditionOrRemoval &&
5220         (aAttribute == nsGkAtoms::alt || aAttribute == nsGkAtoms::value)) {
5221       // We might need to rebuild our alt text.  Just go ahead and
5222       // reconstruct our frame.  This should be quite rare..
5223       return true;
5224     }
5225     return false;
5226   }();
5227 
5228   if (reconstruct) {
5229     retval |= nsChangeHint_ReconstructFrame;
5230   } else if (aAttribute == nsGkAtoms::value) {
5231     retval |= NS_STYLE_HINT_REFLOW;
5232   } else if (aAttribute == nsGkAtoms::size && IsSingleLineTextControl(false)) {
5233     retval |= NS_STYLE_HINT_REFLOW;
5234   }
5235 
5236   return retval;
5237 }
5238 
NS_IMETHODIMP_(bool)5239 NS_IMETHODIMP_(bool)
5240 HTMLInputElement::IsAttributeMapped(const nsAtom* aAttribute) const {
5241   static const MappedAttributeEntry attributes[] = {
5242       {nsGkAtoms::align},
5243       {nullptr},
5244   };
5245 
5246   static const MappedAttributeEntry* const map[] = {
5247       attributes,
5248       sCommonAttributeMap,
5249       sImageMarginSizeAttributeMap,
5250       sImageBorderAttributeMap,
5251   };
5252 
5253   return FindAttributeDependence(aAttribute, map);
5254 }
5255 
GetAttributeMappingFunction() const5256 nsMapRuleToAttributesFunc HTMLInputElement::GetAttributeMappingFunction()
5257     const {
5258   // GetAttributeChangeHint guarantees that changes to mType will trigger a
5259   // reframe, and we update the mapping function in our mapped attrs when our
5260   // type changes, so it's safe to condition our attribute mapping function on
5261   // mType.
5262   if (mType == FormControlType::InputImage) {
5263     return &ImageInputMapAttributesIntoRule;
5264   }
5265 
5266   return &MapCommonAttributesInto;
5267 }
5268 
5269 // Directory picking methods:
5270 
IsFilesAndDirectoriesSupported() const5271 bool HTMLInputElement::IsFilesAndDirectoriesSupported() const {
5272   // This method is supposed to return true if a file and directory picker
5273   // supports the selection of both files and directories *at the same time*.
5274   // Only Mac currently supports that. We could implement it for Mac, but
5275   // currently we do not.
5276   return false;
5277 }
5278 
ChooseDirectory(ErrorResult & aRv)5279 void HTMLInputElement::ChooseDirectory(ErrorResult& aRv) {
5280   if (mType != FormControlType::InputFile) {
5281     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5282     return;
5283   }
5284   // Script can call this method directly, so even though we don't show the
5285   // "Pick Folder..." button on platforms that don't have a directory picker
5286   // we have to redirect to the file picker here.
5287   InitFilePicker(
5288 #if defined(ANDROID)
5289       // No native directory picker - redirect to plain file picker
5290       FILE_PICKER_FILE
5291 #else
5292       FILE_PICKER_DIRECTORY
5293 #endif
5294   );
5295 }
5296 
GetFilesAndDirectories(ErrorResult & aRv)5297 already_AddRefed<Promise> HTMLInputElement::GetFilesAndDirectories(
5298     ErrorResult& aRv) {
5299   if (mType != FormControlType::InputFile) {
5300     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5301     return nullptr;
5302   }
5303 
5304   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
5305   MOZ_ASSERT(global);
5306   if (!global) {
5307     return nullptr;
5308   }
5309 
5310   RefPtr<Promise> p = Promise::Create(global, aRv);
5311   if (aRv.Failed()) {
5312     return nullptr;
5313   }
5314 
5315   const nsTArray<OwningFileOrDirectory>& filesAndDirs =
5316       GetFilesOrDirectoriesInternal();
5317 
5318   Sequence<OwningFileOrDirectory> filesAndDirsSeq;
5319 
5320   if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(),
5321                                  mozilla::fallible_t())) {
5322     p->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
5323     return p.forget();
5324   }
5325 
5326   for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) {
5327     if (filesAndDirs[i].IsDirectory()) {
5328       RefPtr<Directory> directory = filesAndDirs[i].GetAsDirectory();
5329 
5330       // In future we could refactor SetFilePickerFiltersFromAccept to return a
5331       // semicolon separated list of file extensions and include that in the
5332       // filter string passed here.
5333       directory->SetContentFilters(u"filter-out-sensitive"_ns);
5334       filesAndDirsSeq[i].SetAsDirectory() = directory;
5335     } else {
5336       MOZ_ASSERT(filesAndDirs[i].IsFile());
5337 
5338       // This file was directly selected by the user, so don't filter it.
5339       filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i].GetAsFile();
5340     }
5341   }
5342 
5343   p->MaybeResolve(filesAndDirsSeq);
5344   return p.forget();
5345 }
5346 
GetFiles(bool aRecursiveFlag,ErrorResult & aRv)5347 already_AddRefed<Promise> HTMLInputElement::GetFiles(bool aRecursiveFlag,
5348                                                      ErrorResult& aRv) {
5349   if (mType != FormControlType::InputFile) {
5350     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5351     return nullptr;
5352   }
5353 
5354   GetFilesHelper* helper = GetOrCreateGetFilesHelper(aRecursiveFlag, aRv);
5355   if (NS_WARN_IF(aRv.Failed())) {
5356     return nullptr;
5357   }
5358   MOZ_ASSERT(helper);
5359 
5360   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
5361   MOZ_ASSERT(global);
5362   if (!global) {
5363     return nullptr;
5364   }
5365 
5366   RefPtr<Promise> p = Promise::Create(global, aRv);
5367   if (aRv.Failed()) {
5368     return nullptr;
5369   }
5370 
5371   helper->AddPromise(p);
5372   return p.forget();
5373 }
5374 
5375 // Controllers Methods
5376 
GetControllers(ErrorResult & aRv)5377 nsIControllers* HTMLInputElement::GetControllers(ErrorResult& aRv) {
5378   // XXX: what about type "file"?
5379   if (IsSingleLineTextControl(false)) {
5380     if (!mControllers) {
5381       mControllers = new nsXULControllers();
5382       if (!mControllers) {
5383         aRv.Throw(NS_ERROR_FAILURE);
5384         return nullptr;
5385       }
5386 
5387       RefPtr<nsBaseCommandController> commandController =
5388           nsBaseCommandController::CreateEditorController();
5389       if (!commandController) {
5390         aRv.Throw(NS_ERROR_FAILURE);
5391         return nullptr;
5392       }
5393 
5394       mControllers->AppendController(commandController);
5395 
5396       commandController = nsBaseCommandController::CreateEditingController();
5397       if (!commandController) {
5398         aRv.Throw(NS_ERROR_FAILURE);
5399         return nullptr;
5400       }
5401 
5402       mControllers->AppendController(commandController);
5403     }
5404   }
5405 
5406   return mControllers;
5407 }
5408 
GetControllers(nsIControllers ** aResult)5409 nsresult HTMLInputElement::GetControllers(nsIControllers** aResult) {
5410   NS_ENSURE_ARG_POINTER(aResult);
5411 
5412   ErrorResult rv;
5413   RefPtr<nsIControllers> controller = GetControllers(rv);
5414   controller.forget(aResult);
5415   return rv.StealNSResult();
5416 }
5417 
InputTextLength(CallerType aCallerType)5418 int32_t HTMLInputElement::InputTextLength(CallerType aCallerType) {
5419   nsAutoString val;
5420   GetValue(val, aCallerType);
5421   return val.Length();
5422 }
5423 
SetSelectionRange(uint32_t aSelectionStart,uint32_t aSelectionEnd,const Optional<nsAString> & aDirection,ErrorResult & aRv)5424 void HTMLInputElement::SetSelectionRange(uint32_t aSelectionStart,
5425                                          uint32_t aSelectionEnd,
5426                                          const Optional<nsAString>& aDirection,
5427                                          ErrorResult& aRv) {
5428   if (!SupportsTextSelection()) {
5429     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5430     return;
5431   }
5432 
5433   TextControlState* state = GetEditorState();
5434   MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5435   state->SetSelectionRange(aSelectionStart, aSelectionEnd, aDirection, aRv);
5436 }
5437 
SetRangeText(const nsAString & aReplacement,ErrorResult & aRv)5438 void HTMLInputElement::SetRangeText(const nsAString& aReplacement,
5439                                     ErrorResult& aRv) {
5440   if (!SupportsTextSelection()) {
5441     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5442     return;
5443   }
5444 
5445   TextControlState* state = GetEditorState();
5446   MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5447   state->SetRangeText(aReplacement, aRv);
5448 }
5449 
SetRangeText(const nsAString & aReplacement,uint32_t aStart,uint32_t aEnd,SelectionMode aSelectMode,ErrorResult & aRv)5450 void HTMLInputElement::SetRangeText(const nsAString& aReplacement,
5451                                     uint32_t aStart, uint32_t aEnd,
5452                                     SelectionMode aSelectMode,
5453                                     ErrorResult& aRv) {
5454   if (!SupportsTextSelection()) {
5455     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5456     return;
5457   }
5458 
5459   TextControlState* state = GetEditorState();
5460   MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5461   state->SetRangeText(aReplacement, aStart, aEnd, aSelectMode, aRv);
5462 }
5463 
GetValueFromSetRangeText(nsAString & aValue)5464 void HTMLInputElement::GetValueFromSetRangeText(nsAString& aValue) {
5465   GetNonFileValueInternal(aValue);
5466 }
5467 
SetValueFromSetRangeText(const nsAString & aValue)5468 nsresult HTMLInputElement::SetValueFromSetRangeText(const nsAString& aValue) {
5469   return SetValueInternal(aValue, {ValueSetterOption::ByContentAPI,
5470                                    ValueSetterOption::BySetRangeTextAPI,
5471                                    ValueSetterOption::SetValueChanged});
5472 }
5473 
GetSelectionStart(ErrorResult & aRv)5474 Nullable<uint32_t> HTMLInputElement::GetSelectionStart(ErrorResult& aRv) {
5475   if (!SupportsTextSelection()) {
5476     return Nullable<uint32_t>();
5477   }
5478 
5479   uint32_t selStart = GetSelectionStartIgnoringType(aRv);
5480   if (aRv.Failed()) {
5481     return Nullable<uint32_t>();
5482   }
5483 
5484   return Nullable<uint32_t>(selStart);
5485 }
5486 
GetSelectionStartIgnoringType(ErrorResult & aRv)5487 uint32_t HTMLInputElement::GetSelectionStartIgnoringType(ErrorResult& aRv) {
5488   uint32_t selEnd, selStart;
5489   GetSelectionRange(&selStart, &selEnd, aRv);
5490   return selStart;
5491 }
5492 
SetSelectionStart(const Nullable<uint32_t> & aSelectionStart,ErrorResult & aRv)5493 void HTMLInputElement::SetSelectionStart(
5494     const Nullable<uint32_t>& aSelectionStart, ErrorResult& aRv) {
5495   if (!SupportsTextSelection()) {
5496     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5497     return;
5498   }
5499 
5500   TextControlState* state = GetEditorState();
5501   MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5502   state->SetSelectionStart(aSelectionStart, aRv);
5503 }
5504 
GetSelectionEnd(ErrorResult & aRv)5505 Nullable<uint32_t> HTMLInputElement::GetSelectionEnd(ErrorResult& aRv) {
5506   if (!SupportsTextSelection()) {
5507     return Nullable<uint32_t>();
5508   }
5509 
5510   uint32_t selEnd = GetSelectionEndIgnoringType(aRv);
5511   if (aRv.Failed()) {
5512     return Nullable<uint32_t>();
5513   }
5514 
5515   return Nullable<uint32_t>(selEnd);
5516 }
5517 
GetSelectionEndIgnoringType(ErrorResult & aRv)5518 uint32_t HTMLInputElement::GetSelectionEndIgnoringType(ErrorResult& aRv) {
5519   uint32_t selEnd, selStart;
5520   GetSelectionRange(&selStart, &selEnd, aRv);
5521   return selEnd;
5522 }
5523 
SetSelectionEnd(const Nullable<uint32_t> & aSelectionEnd,ErrorResult & aRv)5524 void HTMLInputElement::SetSelectionEnd(const Nullable<uint32_t>& aSelectionEnd,
5525                                        ErrorResult& aRv) {
5526   if (!SupportsTextSelection()) {
5527     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5528     return;
5529   }
5530 
5531   TextControlState* state = GetEditorState();
5532   MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5533   state->SetSelectionEnd(aSelectionEnd, aRv);
5534 }
5535 
GetSelectionRange(uint32_t * aSelectionStart,uint32_t * aSelectionEnd,ErrorResult & aRv)5536 void HTMLInputElement::GetSelectionRange(uint32_t* aSelectionStart,
5537                                          uint32_t* aSelectionEnd,
5538                                          ErrorResult& aRv) {
5539   TextControlState* state = GetEditorState();
5540   if (!state) {
5541     // Not a text control.
5542     aRv.Throw(NS_ERROR_UNEXPECTED);
5543     return;
5544   }
5545 
5546   state->GetSelectionRange(aSelectionStart, aSelectionEnd, aRv);
5547 }
5548 
GetSelectionDirection(nsAString & aDirection,ErrorResult & aRv)5549 void HTMLInputElement::GetSelectionDirection(nsAString& aDirection,
5550                                              ErrorResult& aRv) {
5551   if (!SupportsTextSelection()) {
5552     aDirection.SetIsVoid(true);
5553     return;
5554   }
5555 
5556   TextControlState* state = GetEditorState();
5557   MOZ_ASSERT(state, "SupportsTextSelection came back true!");
5558   state->GetSelectionDirectionString(aDirection, aRv);
5559 }
5560 
SetSelectionDirection(const nsAString & aDirection,ErrorResult & aRv)5561 void HTMLInputElement::SetSelectionDirection(const nsAString& aDirection,
5562                                              ErrorResult& aRv) {
5563   if (!SupportsTextSelection()) {
5564     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5565     return;
5566   }
5567 
5568   TextControlState* state = GetEditorState();
5569   MOZ_ASSERT(state, "SupportsTextSelection came back true!");
5570   state->SetSelectionDirection(aDirection, aRv);
5571 }
5572 
5573 #ifdef ACCESSIBILITY
FireEventForAccessibility(HTMLInputElement * aTarget,EventMessage aEventMessage)5574 /*static*/ nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
5575                                               EventMessage aEventMessage) {
5576   Element* element = static_cast<Element*>(aTarget);
5577   return nsContentUtils::DispatchTrustedEvent<WidgetEvent>(
5578       element->OwnerDoc(), element, aEventMessage, CanBubble::eYes,
5579       Cancelable::eYes);
5580 }
5581 #endif
5582 
UpdateApzAwareFlag()5583 void HTMLInputElement::UpdateApzAwareFlag() {
5584 #if !defined(ANDROID) && !defined(XP_MACOSX)
5585   if (mType == FormControlType::InputNumber ||
5586       mType == FormControlType::InputRange) {
5587     SetMayBeApzAware();
5588   }
5589 #endif
5590 }
5591 
SetDefaultValueAsValue()5592 nsresult HTMLInputElement::SetDefaultValueAsValue() {
5593   NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE,
5594                "GetValueMode() should return VALUE_MODE_VALUE!");
5595 
5596   // The element has a content attribute value different from it's value when
5597   // it's in the value mode value.
5598   nsAutoString resetVal;
5599   GetDefaultValue(resetVal);
5600 
5601   // SetValueInternal is going to sanitize the value.
5602   // TODO(mbrodesser): sanitizing will only happen if `mDoneCreating` is true.
5603   return SetValueInternal(resetVal, ValueSetterOption::ByInternalAPI);
5604 }
5605 
SetDirectionFromValue(bool aNotify)5606 void HTMLInputElement::SetDirectionFromValue(bool aNotify) {
5607   if (IsSingleLineTextControl(true)) {
5608     nsAutoString value;
5609     GetValue(value, CallerType::System);
5610     SetDirectionalityFromValue(this, value, aNotify);
5611   }
5612 }
5613 
5614 NS_IMETHODIMP
Reset()5615 HTMLInputElement::Reset() {
5616   // We should be able to reset all dirty flags regardless of the type.
5617   SetCheckedChanged(false);
5618   SetValueChanged(false);
5619   SetLastValueChangeWasInteractive(false);
5620 
5621   switch (GetValueMode()) {
5622     case VALUE_MODE_VALUE: {
5623       nsresult result = SetDefaultValueAsValue();
5624       if (CreatesDateTimeWidget()) {
5625         // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded`
5626         // can fire a change event if necessary.
5627         GetValue(mFocusedValue, CallerType::System);
5628       }
5629       return result;
5630     }
5631     case VALUE_MODE_DEFAULT_ON:
5632       DoSetChecked(DefaultChecked(), true, false);
5633       return NS_OK;
5634     case VALUE_MODE_FILENAME:
5635       ClearFiles(false);
5636       return NS_OK;
5637     case VALUE_MODE_DEFAULT:
5638     default:
5639       return NS_OK;
5640   }
5641 }
5642 
5643 NS_IMETHODIMP
SubmitNamesValues(HTMLFormSubmission * aFormSubmission)5644 HTMLInputElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission) {
5645   // Disabled elements don't submit
5646   // For type=reset, and type=button, we just never submit, period.
5647   // For type=image and type=button, we only submit if we were the button
5648   // pressed
5649   // For type=radio and type=checkbox, we only submit if checked=true
5650   if (IsDisabled() || mType == FormControlType::InputReset ||
5651       mType == FormControlType::InputButton ||
5652       ((mType == FormControlType::InputSubmit ||
5653         mType == FormControlType::InputImage) &&
5654        aFormSubmission->GetSubmitterElement() != this) ||
5655       ((mType == FormControlType::InputRadio ||
5656         mType == FormControlType::InputCheckbox) &&
5657        !mChecked)) {
5658     return NS_OK;
5659   }
5660 
5661   // Get the name
5662   nsAutoString name;
5663   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
5664 
5665   // Submit .x, .y for input type=image
5666   if (mType == FormControlType::InputImage) {
5667     // Get a property set by the frame to find out where it was clicked.
5668     nsIntPoint* lastClickedPoint =
5669         static_cast<nsIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
5670     int32_t x, y;
5671     if (lastClickedPoint) {
5672       // Convert the values to strings for submission
5673       x = lastClickedPoint->x;
5674       y = lastClickedPoint->y;
5675     } else {
5676       x = y = 0;
5677     }
5678 
5679     nsAutoString xVal, yVal;
5680     xVal.AppendInt(x);
5681     yVal.AppendInt(y);
5682 
5683     if (!name.IsEmpty()) {
5684       aFormSubmission->AddNameValuePair(name + u".x"_ns, xVal);
5685       aFormSubmission->AddNameValuePair(name + u".y"_ns, yVal);
5686     } else {
5687       // If the Image Element has no name, simply return x and y
5688       // to Nav and IE compatibility.
5689       aFormSubmission->AddNameValuePair(u"x"_ns, xVal);
5690       aFormSubmission->AddNameValuePair(u"y"_ns, yVal);
5691     }
5692 
5693     return NS_OK;
5694   }
5695 
5696   // If name not there, don't submit
5697   if (name.IsEmpty()) {
5698     return NS_OK;
5699   }
5700 
5701   //
5702   // Submit file if its input type=file and this encoding method accepts files
5703   //
5704   if (mType == FormControlType::InputFile) {
5705     // Submit files
5706 
5707     const nsTArray<OwningFileOrDirectory>& files =
5708         GetFilesOrDirectoriesInternal();
5709 
5710     if (files.IsEmpty()) {
5711       NS_ENSURE_STATE(GetOwnerGlobal());
5712       ErrorResult rv;
5713       RefPtr<Blob> blob = Blob::CreateStringBlob(
5714           GetOwnerGlobal(), ""_ns, u"application/octet-stream"_ns);
5715       RefPtr<File> file = blob->ToFile(u""_ns, rv);
5716 
5717       if (!rv.Failed()) {
5718         aFormSubmission->AddNameBlobPair(name, file);
5719       }
5720 
5721       return rv.StealNSResult();
5722     }
5723 
5724     for (uint32_t i = 0; i < files.Length(); ++i) {
5725       if (files[i].IsFile()) {
5726         aFormSubmission->AddNameBlobPair(name, files[i].GetAsFile());
5727       } else {
5728         MOZ_ASSERT(files[i].IsDirectory());
5729         aFormSubmission->AddNameDirectoryPair(name, files[i].GetAsDirectory());
5730       }
5731     }
5732 
5733     return NS_OK;
5734   }
5735 
5736   if (mType == FormControlType::InputHidden &&
5737       name.LowerCaseEqualsLiteral("_charset_")) {
5738     nsCString charset;
5739     aFormSubmission->GetCharset(charset);
5740     return aFormSubmission->AddNameValuePair(name,
5741                                              NS_ConvertASCIItoUTF16(charset));
5742   }
5743 
5744   //
5745   // Submit name=value
5746   //
5747 
5748   // Get the value
5749   nsAutoString value;
5750   GetValue(value, CallerType::System);
5751 
5752   if (mType == FormControlType::InputSubmit && value.IsEmpty() &&
5753       !HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
5754     // Get our default value, which is the same as our default label
5755     nsAutoString defaultValue;
5756     nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
5757                                             "Submit", OwnerDoc(), defaultValue);
5758     value = defaultValue;
5759   }
5760 
5761   return aFormSubmission->AddNameValuePair(name, value);
5762 }
5763 
SaveFileContentData(const nsTArray<OwningFileOrDirectory> & aArray)5764 static nsTArray<FileContentData> SaveFileContentData(
5765     const nsTArray<OwningFileOrDirectory>& aArray) {
5766   nsTArray<FileContentData> res(aArray.Length());
5767   for (auto& it : aArray) {
5768     if (it.IsFile()) {
5769       RefPtr<BlobImpl> impl = it.GetAsFile()->Impl();
5770       res.AppendElement(std::move(impl));
5771     } else {
5772       MOZ_ASSERT(it.IsDirectory());
5773       nsString fullPath;
5774       nsresult rv = it.GetAsDirectory()->GetFullRealPath(fullPath);
5775       if (NS_WARN_IF(NS_FAILED(rv))) {
5776         continue;
5777       }
5778       res.AppendElement(std::move(fullPath));
5779     }
5780   }
5781   return res;
5782 }
5783 
5784 NS_IMETHODIMP
SaveState()5785 HTMLInputElement::SaveState() {
5786   PresState* state = nullptr;
5787   switch (GetValueMode()) {
5788     case VALUE_MODE_DEFAULT_ON:
5789       if (mCheckedChanged) {
5790         state = GetPrimaryPresState();
5791         if (!state) {
5792           return NS_OK;
5793         }
5794 
5795         state->contentData() = CheckedContentData(mChecked);
5796       }
5797       break;
5798     case VALUE_MODE_FILENAME:
5799       if (!mFileData->mFilesOrDirectories.IsEmpty()) {
5800         state = GetPrimaryPresState();
5801         if (!state) {
5802           return NS_OK;
5803         }
5804 
5805         state->contentData() =
5806             SaveFileContentData(mFileData->mFilesOrDirectories);
5807       }
5808       break;
5809     case VALUE_MODE_VALUE:
5810     case VALUE_MODE_DEFAULT:
5811       // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
5812       // mType should have never been FormControlType::InputPassword and value
5813       // should have changed.
5814       if ((GetValueMode() == VALUE_MODE_DEFAULT &&
5815            mType != FormControlType::InputHidden) ||
5816           mHasBeenTypePassword || !mValueChanged) {
5817         break;
5818       }
5819 
5820       state = GetPrimaryPresState();
5821       if (!state) {
5822         return NS_OK;
5823       }
5824 
5825       nsAutoString value;
5826       GetValue(value, CallerType::System);
5827 
5828       if (!IsSingleLineTextControl(false)) {
5829         nsresult rv = nsLinebreakConverter::ConvertStringLineBreaks(
5830             value, nsLinebreakConverter::eLinebreakPlatform,
5831             nsLinebreakConverter::eLinebreakContent);
5832 
5833         if (NS_FAILED(rv)) {
5834           NS_ERROR("Converting linebreaks failed!");
5835           return rv;
5836         }
5837       }
5838 
5839       state->contentData() =
5840           TextContentData(value, mLastValueChangeWasInteractive);
5841       break;
5842   }
5843 
5844   if (mDisabledChanged) {
5845     if (!state) {
5846       state = GetPrimaryPresState();
5847     }
5848     if (state) {
5849       // We do not want to save the real disabled state but the disabled
5850       // attribute.
5851       state->disabled() = HasAttr(kNameSpaceID_None, nsGkAtoms::disabled);
5852       state->disabledSet() = true;
5853     }
5854   }
5855 
5856   return NS_OK;
5857 }
5858 
DoneCreatingElement()5859 void HTMLInputElement::DoneCreatingElement() {
5860   mDoneCreating = true;
5861 
5862   //
5863   // Restore state as needed.  Note that disabled state applies to all control
5864   // types.
5865   //
5866   bool restoredCheckedState = false;
5867   if (!mInhibitRestoration) {
5868     GenerateStateKey();
5869     restoredCheckedState = RestoreFormControlState();
5870   }
5871 
5872   //
5873   // If restore does not occur, we initialize .checked using the CHECKED
5874   // property.
5875   //
5876   if (!restoredCheckedState && mShouldInitChecked) {
5877     DoSetChecked(DefaultChecked(), false, false);
5878   }
5879 
5880   // Sanitize the value and potentially set mFocusedValue.
5881   if (GetValueMode() == VALUE_MODE_VALUE) {
5882     nsAutoString aValue;
5883     GetValue(aValue, CallerType::System);
5884     // TODO: What should we do if SetValueInternal fails?  (The allocation
5885     // may potentially be big, but most likely we've failed to allocate
5886     // before the type change.)
5887     SetValueInternal(aValue, ValueSetterOption::ByInternalAPI);
5888 
5889     if (CreatesDateTimeWidget()) {
5890       // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded` can
5891       // fire a change event if necessary.
5892       mFocusedValue = aValue;
5893     }
5894   }
5895 
5896   mShouldInitChecked = false;
5897 }
5898 
DestroyContent()5899 void HTMLInputElement::DestroyContent() {
5900   nsImageLoadingContent::Destroy();
5901   TextControlElement::DestroyContent();
5902 }
5903 
IntrinsicState() const5904 EventStates HTMLInputElement::IntrinsicState() const {
5905   // If you add states here, and they're type-dependent, you need to add them
5906   // to the type case in AfterSetAttr.
5907 
5908   EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
5909   if (mType == FormControlType::InputCheckbox ||
5910       mType == FormControlType::InputRadio) {
5911     // Check current checked state (:checked)
5912     if (mChecked) {
5913       state |= NS_EVENT_STATE_CHECKED;
5914     }
5915 
5916     // Check current indeterminate state (:indeterminate)
5917     if (mType == FormControlType::InputCheckbox && mIndeterminate) {
5918       state |= NS_EVENT_STATE_INDETERMINATE;
5919     }
5920 
5921     if (mType == FormControlType::InputRadio) {
5922       HTMLInputElement* selected = GetSelectedRadioButton();
5923       bool indeterminate = !selected && !mChecked;
5924 
5925       if (indeterminate) {
5926         state |= NS_EVENT_STATE_INDETERMINATE;
5927       }
5928     }
5929 
5930     // Check whether we are the default checked element (:default)
5931     if (DefaultChecked()) {
5932       state |= NS_EVENT_STATE_DEFAULT;
5933     }
5934   } else if (mType == FormControlType::InputImage) {
5935     state |= nsImageLoadingContent::ImageState();
5936   }
5937 
5938   if (IsCandidateForConstraintValidation()) {
5939     if (IsValid()) {
5940       state |= NS_EVENT_STATE_VALID;
5941     } else {
5942       state |= NS_EVENT_STATE_INVALID;
5943 
5944       if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
5945           (mCanShowInvalidUI && ShouldShowValidityUI())) {
5946         state |= NS_EVENT_STATE_MOZ_UI_INVALID;
5947       }
5948     }
5949 
5950     // :-moz-ui-valid applies if all of the following conditions are true:
5951     // 1. The element is not focused, or had either :-moz-ui-valid or
5952     //    :-moz-ui-invalid applying before it was focused ;
5953     // 2. The element is either valid or isn't allowed to have
5954     //    :-moz-ui-invalid applying ;
5955     // 3. The element has already been modified or the user tried to submit the
5956     //    form owner while invalid.
5957     if (mCanShowValidUI && ShouldShowValidityUI() &&
5958         (IsValid() || (!state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
5959                        !mCanShowInvalidUI))) {
5960       state |= NS_EVENT_STATE_MOZ_UI_VALID;
5961     }
5962 
5963     // :in-range and :out-of-range only apply if the element currently has a
5964     // range
5965     if (mHasRange) {
5966       state |= (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
5967                 GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW))
5968                    ? NS_EVENT_STATE_OUTOFRANGE
5969                    : NS_EVENT_STATE_INRANGE;
5970     }
5971   }
5972 
5973   if (PlaceholderApplies() && HasAttr(nsGkAtoms::placeholder) &&
5974       ShouldShowPlaceholder()) {
5975     state |= NS_EVENT_STATE_PLACEHOLDERSHOWN;
5976   }
5977 
5978   if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
5979     state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
5980   }
5981 
5982   return state;
5983 }
5984 
ShouldShowPlaceholder() const5985 bool HTMLInputElement::ShouldShowPlaceholder() const {
5986   MOZ_ASSERT(PlaceholderApplies());
5987   return IsValueEmpty();
5988 }
5989 
RestoreFileContentData(nsPIDOMWindowInner * aWindow,const nsTArray<FileContentData> & aData)5990 static nsTArray<OwningFileOrDirectory> RestoreFileContentData(
5991     nsPIDOMWindowInner* aWindow, const nsTArray<FileContentData>& aData) {
5992   nsTArray<OwningFileOrDirectory> res(aData.Length());
5993   for (auto& it : aData) {
5994     if (it.type() == FileContentData::TBlobImpl) {
5995       if (!it.get_BlobImpl()) {
5996         // Serialization failed, skip this file.
5997         continue;
5998       }
5999 
6000       RefPtr<File> file = File::Create(aWindow->AsGlobal(), it.get_BlobImpl());
6001       if (NS_WARN_IF(!file)) {
6002         continue;
6003       }
6004 
6005       OwningFileOrDirectory* element = res.AppendElement();
6006       element->SetAsFile() = file;
6007     } else {
6008       MOZ_ASSERT(it.type() == FileContentData::TnsString);
6009       nsCOMPtr<nsIFile> file;
6010       nsresult rv =
6011           NS_NewLocalFile(it.get_nsString(), true, getter_AddRefs(file));
6012       if (NS_WARN_IF(NS_FAILED(rv))) {
6013         continue;
6014       }
6015 
6016       RefPtr<Directory> directory =
6017           Directory::Create(aWindow->AsGlobal(), file);
6018       MOZ_ASSERT(directory);
6019 
6020       OwningFileOrDirectory* element = res.AppendElement();
6021       element->SetAsDirectory() = directory;
6022     }
6023   }
6024   return res;
6025 }
6026 
RestoreState(PresState * aState)6027 bool HTMLInputElement::RestoreState(PresState* aState) {
6028   bool restoredCheckedState = false;
6029 
6030   const PresContentData& inputState = aState->contentData();
6031 
6032   switch (GetValueMode()) {
6033     case VALUE_MODE_DEFAULT_ON:
6034       if (inputState.type() == PresContentData::TCheckedContentData) {
6035         restoredCheckedState = true;
6036         bool checked = inputState.get_CheckedContentData().checked();
6037         DoSetChecked(checked, true, true);
6038       }
6039       break;
6040     case VALUE_MODE_FILENAME:
6041       if (inputState.type() == PresContentData::TArrayOfFileContentData) {
6042         nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
6043         if (window) {
6044           nsTArray<OwningFileOrDirectory> array =
6045               RestoreFileContentData(window, inputState);
6046           SetFilesOrDirectories(array, true);
6047         }
6048       }
6049       break;
6050     case VALUE_MODE_VALUE:
6051     case VALUE_MODE_DEFAULT:
6052       if (GetValueMode() == VALUE_MODE_DEFAULT &&
6053           mType != FormControlType::InputHidden) {
6054         break;
6055       }
6056 
6057       if (inputState.type() == PresContentData::TTextContentData) {
6058         // TODO: What should we do if SetValueInternal fails?  (The allocation
6059         // may potentially be big, but most likely we've failed to allocate
6060         // before the type change.)
6061         SetValueInternal(inputState.get_TextContentData().value(),
6062                          ValueSetterOption::SetValueChanged);
6063         if (inputState.get_TextContentData().lastValueChangeWasInteractive()) {
6064           SetLastValueChangeWasInteractive(true);
6065         }
6066       }
6067       break;
6068   }
6069 
6070   if (aState->disabledSet() && !aState->disabled()) {
6071     SetDisabled(false, IgnoreErrors());
6072   }
6073 
6074   return restoredCheckedState;
6075 }
6076 
AllowDrop()6077 bool HTMLInputElement::AllowDrop() {
6078   // Allow drop on anything other than file inputs.
6079 
6080   return mType != FormControlType::InputFile;
6081 }
6082 
6083 /*
6084  * Radio group stuff
6085  */
6086 
AddedToRadioGroup()6087 void HTMLInputElement::AddedToRadioGroup() {
6088   // If the element is neither in a form nor a document, there is no group so we
6089   // should just stop here.
6090   if (!mForm && (!GetUncomposedDocOrConnectedShadowRoot() ||
6091                  IsInNativeAnonymousSubtree())) {
6092     return;
6093   }
6094 
6095   // Make sure not to notify if we're still being created
6096   bool notify = mDoneCreating;
6097 
6098   //
6099   // If the input element is checked, and we add it to the group, it will
6100   // deselect whatever is currently selected in that group
6101   //
6102   if (mChecked) {
6103     //
6104     // If it is checked, call "RadioSetChecked" to perform the selection/
6105     // deselection ritual.  This has the side effect of repainting the
6106     // radio button, but as adding a checked radio button into the group
6107     // should not be that common an occurrence, I think we can live with
6108     // that.
6109     //
6110     RadioSetChecked(notify);
6111   }
6112 
6113   //
6114   // For integrity purposes, we have to ensure that "checkedChanged" is
6115   // the same for this new element as for all the others in the group
6116   //
6117   bool checkedChanged = mCheckedChanged;
6118 
6119   nsCOMPtr<nsIRadioVisitor> visitor =
6120       new nsRadioGetCheckedChangedVisitor(&checkedChanged, this);
6121   VisitGroup(visitor);
6122 
6123   SetCheckedChangedInternal(checkedChanged);
6124 
6125   //
6126   // Add the radio to the radio group container.
6127   //
6128   nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
6129   if (container) {
6130     nsAutoString name;
6131     GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6132     container->AddToRadioGroup(name, this);
6133 
6134     // We initialize the validity of the element to the validity of the group
6135     // because we assume UpdateValueMissingState() will be called after.
6136     SetValidityState(VALIDITY_STATE_VALUE_MISSING,
6137                      container->GetValueMissingState(name));
6138   }
6139 }
6140 
WillRemoveFromRadioGroup()6141 void HTMLInputElement::WillRemoveFromRadioGroup() {
6142   nsIRadioGroupContainer* container = GetRadioGroupContainer();
6143   if (!container) {
6144     return;
6145   }
6146 
6147   nsAutoString name;
6148   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6149 
6150   // If this button was checked, we need to notify the group that there is no
6151   // longer a selected radio button
6152   if (mChecked) {
6153     container->SetCurrentRadioButton(name, nullptr);
6154 
6155     nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
6156     VisitGroup(visitor);
6157   }
6158 
6159   // Remove this radio from its group in the container.
6160   // We need to call UpdateValueMissingValidityStateForRadio before to make sure
6161   // the group validity is updated (with this element being ignored).
6162   UpdateValueMissingValidityStateForRadio(true);
6163   container->RemoveFromRadioGroup(name, this);
6164 }
6165 
IsHTMLFocusable(bool aWithMouse,bool * aIsFocusable,int32_t * aTabIndex)6166 bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
6167                                        int32_t* aTabIndex) {
6168   if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(
6169           aWithMouse, aIsFocusable, aTabIndex)) {
6170     return true;
6171   }
6172 
6173   if (IsDisabled()) {
6174     *aIsFocusable = false;
6175     return true;
6176   }
6177 
6178   if (IsSingleLineTextControl(false) || mType == FormControlType::InputRange) {
6179     *aIsFocusable = true;
6180     return false;
6181   }
6182 
6183   const bool defaultFocusable = IsFormControlDefaultFocusable(aWithMouse);
6184   if (CreatesDateTimeWidget()) {
6185     if (aTabIndex) {
6186       // We only want our native anonymous child to be tabable to, not ourself.
6187       *aTabIndex = -1;
6188     }
6189     *aIsFocusable = true;
6190     return true;
6191   }
6192 
6193   if (mType == FormControlType::InputHidden) {
6194     if (aTabIndex) {
6195       *aTabIndex = -1;
6196     }
6197     *aIsFocusable = false;
6198     return false;
6199   }
6200 
6201   if (!aTabIndex) {
6202     // The other controls are all focusable
6203     *aIsFocusable = defaultFocusable;
6204     return false;
6205   }
6206 
6207   if (mType != FormControlType::InputRadio) {
6208     *aIsFocusable = defaultFocusable;
6209     return false;
6210   }
6211 
6212   if (mChecked) {
6213     // Selected radio buttons are tabbable
6214     *aIsFocusable = defaultFocusable;
6215     return false;
6216   }
6217 
6218   // Current radio button is not selected.
6219   // But make it tabbable if nothing in group is selected.
6220   nsIRadioGroupContainer* container = GetRadioGroupContainer();
6221   if (!container) {
6222     *aIsFocusable = defaultFocusable;
6223     return false;
6224   }
6225 
6226   nsAutoString name;
6227   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6228 
6229   if (container->GetCurrentRadioButton(name)) {
6230     *aTabIndex = -1;
6231   }
6232   *aIsFocusable = defaultFocusable;
6233   return false;
6234 }
6235 
VisitGroup(nsIRadioVisitor * aVisitor)6236 nsresult HTMLInputElement::VisitGroup(nsIRadioVisitor* aVisitor) {
6237   nsIRadioGroupContainer* container = GetRadioGroupContainer();
6238   if (container) {
6239     nsAutoString name;
6240     GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6241     return container->WalkRadioGroup(name, aVisitor);
6242   }
6243 
6244   aVisitor->Visit(this);
6245   return NS_OK;
6246 }
6247 
GetValueMode() const6248 HTMLInputElement::ValueModeType HTMLInputElement::GetValueMode() const {
6249   switch (mType) {
6250     case FormControlType::InputHidden:
6251     case FormControlType::InputSubmit:
6252     case FormControlType::InputButton:
6253     case FormControlType::InputReset:
6254     case FormControlType::InputImage:
6255       return VALUE_MODE_DEFAULT;
6256     case FormControlType::InputCheckbox:
6257     case FormControlType::InputRadio:
6258       return VALUE_MODE_DEFAULT_ON;
6259     case FormControlType::InputFile:
6260       return VALUE_MODE_FILENAME;
6261 #ifdef DEBUG
6262     case FormControlType::InputText:
6263     case FormControlType::InputPassword:
6264     case FormControlType::InputSearch:
6265     case FormControlType::InputTel:
6266     case FormControlType::InputEmail:
6267     case FormControlType::InputUrl:
6268     case FormControlType::InputNumber:
6269     case FormControlType::InputRange:
6270     case FormControlType::InputDate:
6271     case FormControlType::InputTime:
6272     case FormControlType::InputColor:
6273     case FormControlType::InputMonth:
6274     case FormControlType::InputWeek:
6275     case FormControlType::InputDatetimeLocal:
6276       return VALUE_MODE_VALUE;
6277     default:
6278       MOZ_ASSERT_UNREACHABLE("Unexpected input type in GetValueMode()");
6279       return VALUE_MODE_VALUE;
6280 #else   // DEBUG
6281     default:
6282       return VALUE_MODE_VALUE;
6283 #endif  // DEBUG
6284   }
6285 }
6286 
IsMutable() const6287 bool HTMLInputElement::IsMutable() const {
6288   return !IsDisabled() && !(DoesReadOnlyApply() &&
6289                             HasAttr(kNameSpaceID_None, nsGkAtoms::readonly));
6290 }
6291 
DoesRequiredApply() const6292 bool HTMLInputElement::DoesRequiredApply() const {
6293   switch (mType) {
6294     case FormControlType::InputHidden:
6295     case FormControlType::InputButton:
6296     case FormControlType::InputImage:
6297     case FormControlType::InputReset:
6298     case FormControlType::InputSubmit:
6299     case FormControlType::InputRange:
6300     case FormControlType::InputColor:
6301       return false;
6302 #ifdef DEBUG
6303     case FormControlType::InputRadio:
6304     case FormControlType::InputCheckbox:
6305     case FormControlType::InputFile:
6306     case FormControlType::InputText:
6307     case FormControlType::InputPassword:
6308     case FormControlType::InputSearch:
6309     case FormControlType::InputTel:
6310     case FormControlType::InputEmail:
6311     case FormControlType::InputUrl:
6312     case FormControlType::InputNumber:
6313     case FormControlType::InputDate:
6314     case FormControlType::InputTime:
6315     case FormControlType::InputMonth:
6316     case FormControlType::InputWeek:
6317     case FormControlType::InputDatetimeLocal:
6318       return true;
6319     default:
6320       MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6321       return true;
6322 #else   // DEBUG
6323     default:
6324       return true;
6325 #endif  // DEBUG
6326   }
6327 }
6328 
PlaceholderApplies() const6329 bool HTMLInputElement::PlaceholderApplies() const {
6330   if (IsDateTimeInputType(mType)) {
6331     return false;
6332   }
6333   return IsSingleLineTextControl(false);
6334 }
6335 
DoesMinMaxApply() const6336 bool HTMLInputElement::DoesMinMaxApply() const {
6337   switch (mType) {
6338     case FormControlType::InputNumber:
6339     case FormControlType::InputDate:
6340     case FormControlType::InputTime:
6341     case FormControlType::InputRange:
6342     case FormControlType::InputMonth:
6343     case FormControlType::InputWeek:
6344     case FormControlType::InputDatetimeLocal:
6345       return true;
6346 #ifdef DEBUG
6347     case FormControlType::InputReset:
6348     case FormControlType::InputSubmit:
6349     case FormControlType::InputImage:
6350     case FormControlType::InputButton:
6351     case FormControlType::InputHidden:
6352     case FormControlType::InputRadio:
6353     case FormControlType::InputCheckbox:
6354     case FormControlType::InputFile:
6355     case FormControlType::InputText:
6356     case FormControlType::InputPassword:
6357     case FormControlType::InputSearch:
6358     case FormControlType::InputTel:
6359     case FormControlType::InputEmail:
6360     case FormControlType::InputUrl:
6361     case FormControlType::InputColor:
6362       return false;
6363     default:
6364       MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6365       return false;
6366 #else   // DEBUG
6367     default:
6368       return false;
6369 #endif  // DEBUG
6370   }
6371 }
6372 
DoesAutocompleteApply() const6373 bool HTMLInputElement::DoesAutocompleteApply() const {
6374   switch (mType) {
6375     case FormControlType::InputHidden:
6376     case FormControlType::InputText:
6377     case FormControlType::InputSearch:
6378     case FormControlType::InputUrl:
6379     case FormControlType::InputTel:
6380     case FormControlType::InputEmail:
6381     case FormControlType::InputPassword:
6382     case FormControlType::InputDate:
6383     case FormControlType::InputTime:
6384     case FormControlType::InputNumber:
6385     case FormControlType::InputRange:
6386     case FormControlType::InputColor:
6387     case FormControlType::InputMonth:
6388     case FormControlType::InputWeek:
6389     case FormControlType::InputDatetimeLocal:
6390       return true;
6391 #ifdef DEBUG
6392     case FormControlType::InputReset:
6393     case FormControlType::InputSubmit:
6394     case FormControlType::InputImage:
6395     case FormControlType::InputButton:
6396     case FormControlType::InputRadio:
6397     case FormControlType::InputCheckbox:
6398     case FormControlType::InputFile:
6399       return false;
6400     default:
6401       MOZ_ASSERT_UNREACHABLE(
6402           "Unexpected input type in DoesAutocompleteApply()");
6403       return false;
6404 #else   // DEBUG
6405     default:
6406       return false;
6407 #endif  // DEBUG
6408   }
6409 }
6410 
GetStep() const6411 Decimal HTMLInputElement::GetStep() const {
6412   MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
6413 
6414   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::step)) {
6415     return GetDefaultStep() * GetStepScaleFactor();
6416   }
6417 
6418   nsAutoString stepStr;
6419   GetAttr(kNameSpaceID_None, nsGkAtoms::step, stepStr);
6420 
6421   if (stepStr.LowerCaseEqualsLiteral("any")) {
6422     // The element can't suffer from step mismatch if there is no step.
6423     return kStepAny;
6424   }
6425 
6426   Decimal step = StringToDecimal(stepStr);
6427   if (!step.isFinite() || step <= Decimal(0)) {
6428     step = GetDefaultStep();
6429   }
6430 
6431   // For input type=date, we round the step value to have a rounded day.
6432   if (mType == FormControlType::InputDate ||
6433       mType == FormControlType::InputMonth ||
6434       mType == FormControlType::InputWeek) {
6435     step = std::max(step.round(), Decimal(1));
6436   }
6437 
6438   return step * GetStepScaleFactor();
6439 }
6440 
6441 // nsIConstraintValidation
6442 
SetCustomValidity(const nsAString & aError)6443 void HTMLInputElement::SetCustomValidity(const nsAString& aError) {
6444   nsIConstraintValidation::SetCustomValidity(aError);
6445 
6446   UpdateState(true);
6447 }
6448 
IsTooLong()6449 bool HTMLInputElement::IsTooLong() {
6450   if (!mValueChanged || !mLastValueChangeWasInteractive) {
6451     return false;
6452   }
6453 
6454   return mInputType->IsTooLong();
6455 }
6456 
IsTooShort()6457 bool HTMLInputElement::IsTooShort() {
6458   if (!mValueChanged || !mLastValueChangeWasInteractive) {
6459     return false;
6460   }
6461 
6462   return mInputType->IsTooShort();
6463 }
6464 
IsValueMissing() const6465 bool HTMLInputElement::IsValueMissing() const {
6466   // Should use UpdateValueMissingValidityStateForRadio() for type radio.
6467   MOZ_ASSERT(mType != FormControlType::InputRadio);
6468 
6469   return mInputType->IsValueMissing();
6470 }
6471 
HasTypeMismatch() const6472 bool HTMLInputElement::HasTypeMismatch() const {
6473   return mInputType->HasTypeMismatch();
6474 }
6475 
HasPatternMismatch() const6476 Maybe<bool> HTMLInputElement::HasPatternMismatch() const {
6477   return mInputType->HasPatternMismatch();
6478 }
6479 
IsRangeOverflow() const6480 bool HTMLInputElement::IsRangeOverflow() const {
6481   return mInputType->IsRangeOverflow();
6482 }
6483 
IsRangeUnderflow() const6484 bool HTMLInputElement::IsRangeUnderflow() const {
6485   return mInputType->IsRangeUnderflow();
6486 }
6487 
HasStepMismatch(bool aUseZeroIfValueNaN) const6488 bool HTMLInputElement::HasStepMismatch(bool aUseZeroIfValueNaN) const {
6489   return mInputType->HasStepMismatch(aUseZeroIfValueNaN);
6490 }
6491 
HasBadInput() const6492 bool HTMLInputElement::HasBadInput() const { return mInputType->HasBadInput(); }
6493 
UpdateTooLongValidityState()6494 void HTMLInputElement::UpdateTooLongValidityState() {
6495   SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
6496 }
6497 
UpdateTooShortValidityState()6498 void HTMLInputElement::UpdateTooShortValidityState() {
6499   SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort());
6500 }
6501 
UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf)6502 void HTMLInputElement::UpdateValueMissingValidityStateForRadio(
6503     bool aIgnoreSelf) {
6504   MOZ_ASSERT(mType == FormControlType::InputRadio,
6505              "This should be called only for radio input types");
6506 
6507   HTMLInputElement* selection = GetSelectedRadioButton();
6508 
6509   aIgnoreSelf = aIgnoreSelf || !IsMutable();
6510 
6511   // If there is no selection, that might mean the radio is not in a group.
6512   // In that case, we can look for the checked state of the radio.
6513   bool selected = selection || (!aIgnoreSelf && mChecked);
6514   bool required = !aIgnoreSelf && IsRequired();
6515   bool valueMissing = false;
6516 
6517   nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
6518 
6519   if (!container) {
6520     SetValidityState(VALIDITY_STATE_VALUE_MISSING,
6521                      IsMutable() && required && !selected);
6522     return;
6523   }
6524 
6525   nsAutoString name;
6526   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6527 
6528   // If the current radio is required and not ignored, we can assume the entire
6529   // group is required.
6530   if (!required) {
6531     required = (aIgnoreSelf && IsRequired())
6532                    ? container->GetRequiredRadioCount(name) - 1
6533                    : container->GetRequiredRadioCount(name);
6534   }
6535 
6536   valueMissing = required && !selected;
6537 
6538   if (container->GetValueMissingState(name) != valueMissing) {
6539     container->SetValueMissingState(name, valueMissing);
6540 
6541     SetValidityState(VALIDITY_STATE_VALUE_MISSING, valueMissing);
6542 
6543     // nsRadioSetValueMissingState will call ContentStateChanged while visiting.
6544     nsAutoScriptBlocker scriptBlocker;
6545     nsCOMPtr<nsIRadioVisitor> visitor =
6546         new nsRadioSetValueMissingState(this, valueMissing);
6547     VisitGroup(visitor);
6548   }
6549 }
6550 
UpdateValueMissingValidityState()6551 void HTMLInputElement::UpdateValueMissingValidityState() {
6552   if (mType == FormControlType::InputRadio) {
6553     UpdateValueMissingValidityStateForRadio(false);
6554     return;
6555   }
6556 
6557   SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
6558 }
6559 
UpdateTypeMismatchValidityState()6560 void HTMLInputElement::UpdateTypeMismatchValidityState() {
6561   SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, HasTypeMismatch());
6562 }
6563 
UpdatePatternMismatchValidityState()6564 void HTMLInputElement::UpdatePatternMismatchValidityState() {
6565   Maybe<bool> hasMismatch = HasPatternMismatch();
6566   // Don't update if the JS engine failed to evaluate it.
6567   if (hasMismatch.isSome()) {
6568     SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, hasMismatch.value());
6569   }
6570 }
6571 
UpdateRangeOverflowValidityState()6572 void HTMLInputElement::UpdateRangeOverflowValidityState() {
6573   SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, IsRangeOverflow());
6574 }
6575 
UpdateRangeUnderflowValidityState()6576 void HTMLInputElement::UpdateRangeUnderflowValidityState() {
6577   SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, IsRangeUnderflow());
6578 }
6579 
UpdateStepMismatchValidityState()6580 void HTMLInputElement::UpdateStepMismatchValidityState() {
6581   SetValidityState(VALIDITY_STATE_STEP_MISMATCH, HasStepMismatch());
6582 }
6583 
UpdateBadInputValidityState()6584 void HTMLInputElement::UpdateBadInputValidityState() {
6585   SetValidityState(VALIDITY_STATE_BAD_INPUT, HasBadInput());
6586 }
6587 
UpdateAllValidityStates(bool aNotify)6588 void HTMLInputElement::UpdateAllValidityStates(bool aNotify) {
6589   bool validBefore = IsValid();
6590   UpdateAllValidityStatesButNotElementState();
6591 
6592   if (validBefore != IsValid()) {
6593     UpdateState(aNotify);
6594   }
6595 }
6596 
UpdateAllValidityStatesButNotElementState()6597 void HTMLInputElement::UpdateAllValidityStatesButNotElementState() {
6598   UpdateTooLongValidityState();
6599   UpdateTooShortValidityState();
6600   UpdateValueMissingValidityState();
6601   UpdateTypeMismatchValidityState();
6602   UpdatePatternMismatchValidityState();
6603   UpdateRangeOverflowValidityState();
6604   UpdateRangeUnderflowValidityState();
6605   UpdateStepMismatchValidityState();
6606   UpdateBadInputValidityState();
6607 }
6608 
UpdateBarredFromConstraintValidation()6609 void HTMLInputElement::UpdateBarredFromConstraintValidation() {
6610   SetBarredFromConstraintValidation(
6611       mType == FormControlType::InputHidden ||
6612       mType == FormControlType::InputButton ||
6613       mType == FormControlType::InputReset ||
6614       HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) || IsDisabled());
6615 }
6616 
GetValidationMessage(nsAString & aValidationMessage,ValidityStateType aType)6617 nsresult HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
6618                                                 ValidityStateType aType) {
6619   return mInputType->GetValidationMessage(aValidationMessage, aType);
6620 }
6621 
IsSingleLineTextControl() const6622 bool HTMLInputElement::IsSingleLineTextControl() const {
6623   return IsSingleLineTextControl(false);
6624 }
6625 
IsTextArea() const6626 bool HTMLInputElement::IsTextArea() const { return false; }
6627 
IsPasswordTextControl() const6628 bool HTMLInputElement::IsPasswordTextControl() const {
6629   return mType == FormControlType::InputPassword;
6630 }
6631 
GetCols()6632 int32_t HTMLInputElement::GetCols() {
6633   // Else we know (assume) it is an input with size attr
6634   const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::size);
6635   if (attr && attr->Type() == nsAttrValue::eInteger) {
6636     int32_t cols = attr->GetIntegerValue();
6637     if (cols > 0) {
6638       return cols;
6639     }
6640   }
6641 
6642   return DEFAULT_COLS;
6643 }
6644 
GetWrapCols()6645 int32_t HTMLInputElement::GetWrapCols() {
6646   return 0;  // only textarea's can have wrap cols
6647 }
6648 
GetRows()6649 int32_t HTMLInputElement::GetRows() { return DEFAULT_ROWS; }
6650 
GetDefaultValueFromContent(nsAString & aValue)6651 void HTMLInputElement::GetDefaultValueFromContent(nsAString& aValue) {
6652   TextControlState* state = GetEditorState();
6653   if (state) {
6654     GetDefaultValue(aValue);
6655     // This is called by the frame to show the value.
6656     // We have to sanitize it when needed.
6657     if (mDoneCreating) {
6658       SanitizeValue(aValue);
6659     }
6660   }
6661 }
6662 
ValueChanged() const6663 bool HTMLInputElement::ValueChanged() const { return mValueChanged; }
6664 
GetTextEditorValue(nsAString & aValue,bool aIgnoreWrap) const6665 void HTMLInputElement::GetTextEditorValue(nsAString& aValue,
6666                                           bool aIgnoreWrap) const {
6667   TextControlState* state = GetEditorState();
6668   if (state) {
6669     state->GetValue(aValue, aIgnoreWrap);
6670   }
6671 }
6672 
TextEditorValueEquals(const nsAString & aValue) const6673 bool HTMLInputElement::TextEditorValueEquals(const nsAString& aValue) const {
6674   TextControlState* state = GetEditorState();
6675   return state ? state->ValueEquals(aValue) : aValue.IsEmpty();
6676 }
6677 
InitializeKeyboardEventListeners()6678 void HTMLInputElement::InitializeKeyboardEventListeners() {
6679   TextControlState* state = GetEditorState();
6680   if (state) {
6681     state->InitializeKeyboardEventListeners();
6682   }
6683 }
6684 
OnValueChanged(ValueChangeKind aKind)6685 void HTMLInputElement::OnValueChanged(ValueChangeKind aKind) {
6686   if (aKind != ValueChangeKind::Internal) {
6687     mLastValueChangeWasInteractive = aKind == ValueChangeKind::UserInteraction;
6688   }
6689 
6690   UpdateAllValidityStates(true);
6691 
6692   if (HasDirAuto()) {
6693     SetDirectionFromValue(true);
6694   }
6695 
6696   // :placeholder-shown pseudo-class may change when the value changes.
6697   // However, we don't want to waste cycles if the state doesn't apply.
6698   if (PlaceholderApplies() && HasAttr(nsGkAtoms::placeholder)) {
6699     UpdateState(true);
6700   }
6701 
6702   // Update clear button state on search inputs
6703   if (mType == FormControlType::InputSearch) {
6704     if (nsSearchControlFrame* searchControlFrame =
6705             do_QueryFrame(GetPrimaryFrame())) {
6706       searchControlFrame->UpdateClearButtonState();
6707     }
6708   }
6709 }
6710 
HasCachedSelection()6711 bool HTMLInputElement::HasCachedSelection() {
6712   TextControlState* state = GetEditorState();
6713   if (!state) {
6714     return false;
6715   }
6716   return state->IsSelectionCached() && state->HasNeverInitializedBefore() &&
6717          state->GetSelectionProperties().GetStart() !=
6718              state->GetSelectionProperties().GetEnd();
6719 }
6720 
FieldSetDisabledChanged(bool aNotify)6721 void HTMLInputElement::FieldSetDisabledChanged(bool aNotify) {
6722   // This *has* to be called *before* UpdateBarredFromConstraintValidation and
6723   // UpdateValueMissingValidityState because these two functions depend on our
6724   // disabled state.
6725   nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
6726 
6727   UpdateValueMissingValidityState();
6728   UpdateBarredFromConstraintValidation();
6729   UpdateState(aNotify);
6730 }
6731 
SetFilePickerFiltersFromAccept(nsIFilePicker * filePicker)6732 void HTMLInputElement::SetFilePickerFiltersFromAccept(
6733     nsIFilePicker* filePicker) {
6734   // We always add |filterAll|
6735   filePicker->AppendFilters(nsIFilePicker::filterAll);
6736 
6737   NS_ASSERTION(HasAttr(kNameSpaceID_None, nsGkAtoms::accept),
6738                "You should not call SetFilePickerFiltersFromAccept if the"
6739                " element has no accept attribute!");
6740 
6741   // Services to retrieve image/*, audio/*, video/* filters
6742   nsCOMPtr<nsIStringBundleService> stringService =
6743       mozilla::components::StringBundle::Service();
6744   if (!stringService) {
6745     return;
6746   }
6747   nsCOMPtr<nsIStringBundle> filterBundle;
6748   if (NS_FAILED(stringService->CreateBundle(
6749           "chrome://global/content/filepicker.properties",
6750           getter_AddRefs(filterBundle)))) {
6751     return;
6752   }
6753 
6754   // Service to retrieve mime type information for mime types filters
6755   nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
6756   if (!mimeService) {
6757     return;
6758   }
6759 
6760   nsAutoString accept;
6761   GetAttr(kNameSpaceID_None, nsGkAtoms::accept, accept);
6762 
6763   HTMLSplitOnSpacesTokenizer tokenizer(accept, ',');
6764 
6765   nsTArray<nsFilePickerFilter> filters;
6766   nsString allExtensionsList;
6767 
6768   bool allMimeTypeFiltersAreValid = true;
6769   bool atLeastOneFileExtensionFilter = false;
6770 
6771   // Retrieve all filters
6772   while (tokenizer.hasMoreTokens()) {
6773     const nsDependentSubstring& token = tokenizer.nextToken();
6774 
6775     if (token.IsEmpty()) {
6776       continue;
6777     }
6778 
6779     int32_t filterMask = 0;
6780     nsString filterName;
6781     nsString extensionListStr;
6782 
6783     // First, check for image/audio/video filters...
6784     if (token.EqualsLiteral("image/*")) {
6785       filterMask = nsIFilePicker::filterImages;
6786       filterBundle->GetStringFromName("imageFilter", extensionListStr);
6787     } else if (token.EqualsLiteral("audio/*")) {
6788       filterMask = nsIFilePicker::filterAudio;
6789       filterBundle->GetStringFromName("audioFilter", extensionListStr);
6790     } else if (token.EqualsLiteral("video/*")) {
6791       filterMask = nsIFilePicker::filterVideo;
6792       filterBundle->GetStringFromName("videoFilter", extensionListStr);
6793     } else if (token.First() == '.') {
6794       if (token.Contains(';') || token.Contains('*')) {
6795         // Ignore this filter as it contains reserved characters
6796         continue;
6797       }
6798       extensionListStr = u"*"_ns + token;
6799       filterName = extensionListStr;
6800       atLeastOneFileExtensionFilter = true;
6801     } else {
6802       //... if no image/audio/video filter is found, check mime types filters
6803       nsCOMPtr<nsIMIMEInfo> mimeInfo;
6804       if (NS_FAILED(
6805               mimeService->GetFromTypeAndExtension(NS_ConvertUTF16toUTF8(token),
6806                                                    ""_ns,  // No extension
6807                                                    getter_AddRefs(mimeInfo))) ||
6808           !mimeInfo) {
6809         allMimeTypeFiltersAreValid = false;
6810         continue;
6811       }
6812 
6813       // Get a name for the filter: first try the description, then the mime
6814       // type name if there is no description
6815       mimeInfo->GetDescription(filterName);
6816       if (filterName.IsEmpty()) {
6817         nsCString mimeTypeName;
6818         mimeInfo->GetType(mimeTypeName);
6819         CopyUTF8toUTF16(mimeTypeName, filterName);
6820       }
6821 
6822       // Get extension list
6823       nsCOMPtr<nsIUTF8StringEnumerator> extensions;
6824       mimeInfo->GetFileExtensions(getter_AddRefs(extensions));
6825 
6826       bool hasMore;
6827       while (NS_SUCCEEDED(extensions->HasMore(&hasMore)) && hasMore) {
6828         nsCString extension;
6829         if (NS_FAILED(extensions->GetNext(extension))) {
6830           continue;
6831         }
6832         if (!extensionListStr.IsEmpty()) {
6833           extensionListStr.AppendLiteral("; ");
6834         }
6835         extensionListStr += u"*."_ns + NS_ConvertUTF8toUTF16(extension);
6836       }
6837     }
6838 
6839     if (!filterMask && (extensionListStr.IsEmpty() || filterName.IsEmpty())) {
6840       // No valid filter found
6841       allMimeTypeFiltersAreValid = false;
6842       continue;
6843     }
6844 
6845     // At this point we're sure the token represents a valid filter, so pass
6846     // it directly as a raw filter.
6847     filePicker->AppendRawFilter(token);
6848 
6849     // If we arrived here, that means we have a valid filter: let's create it
6850     // and add it to our list, if no similar filter is already present
6851     nsFilePickerFilter filter;
6852     if (filterMask) {
6853       filter = nsFilePickerFilter(filterMask);
6854     } else {
6855       filter = nsFilePickerFilter(filterName, extensionListStr);
6856     }
6857 
6858     if (!filters.Contains(filter)) {
6859       if (!allExtensionsList.IsEmpty()) {
6860         allExtensionsList.AppendLiteral("; ");
6861       }
6862       allExtensionsList += extensionListStr;
6863       filters.AppendElement(filter);
6864     }
6865   }
6866 
6867   // Remove similar filters
6868   // Iterate over a copy, as we might modify the original filters list
6869   const nsTArray<nsFilePickerFilter> filtersCopy = filters.Clone();
6870   for (uint32_t i = 0; i < filtersCopy.Length(); ++i) {
6871     const nsFilePickerFilter& filterToCheck = filtersCopy[i];
6872     if (filterToCheck.mFilterMask) {
6873       continue;
6874     }
6875     for (uint32_t j = 0; j < filtersCopy.Length(); ++j) {
6876       if (i == j) {
6877         continue;
6878       }
6879       // Check if this filter's extension list is a substring of the other one.
6880       // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
6881       // be removed.
6882       // Add an extra "; " to be sure the check will work and avoid cases like
6883       // "*.xls" being a subtring of "*.xslx" while those are two differents
6884       // filters and none should be removed.
6885       if (FindInReadable(filterToCheck.mFilter + u";"_ns,
6886                          filtersCopy[j].mFilter + u";"_ns)) {
6887         // We already have a similar, less restrictive filter (i.e.
6888         // filterToCheck extensionList is just a subset of another filter
6889         // extension list): remove this one
6890         filters.RemoveElement(filterToCheck);
6891       }
6892     }
6893   }
6894 
6895   // Add "All Supported Types" filter
6896   if (filters.Length() > 1) {
6897     nsAutoString title;
6898     nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
6899                                        "AllSupportedTypes", title);
6900     filePicker->AppendFilter(title, allExtensionsList);
6901   }
6902 
6903   // Add each filter
6904   for (uint32_t i = 0; i < filters.Length(); ++i) {
6905     const nsFilePickerFilter& filter = filters[i];
6906     if (filter.mFilterMask) {
6907       filePicker->AppendFilters(filter.mFilterMask);
6908     } else {
6909       filePicker->AppendFilter(filter.mTitle, filter.mFilter);
6910     }
6911   }
6912 
6913   if (filters.Length() >= 1 &&
6914       (allMimeTypeFiltersAreValid || atLeastOneFileExtensionFilter)) {
6915     // |filterAll| will always use index=0 so we need to set index=1 as the
6916     // current filter.
6917     filePicker->SetFilterIndex(1);
6918   }
6919 }
6920 
GetStepScaleFactor() const6921 Decimal HTMLInputElement::GetStepScaleFactor() const {
6922   MOZ_ASSERT(DoesStepApply());
6923 
6924   switch (mType) {
6925     case FormControlType::InputDate:
6926       return kStepScaleFactorDate;
6927     case FormControlType::InputNumber:
6928     case FormControlType::InputRange:
6929       return kStepScaleFactorNumberRange;
6930     case FormControlType::InputTime:
6931     case FormControlType::InputDatetimeLocal:
6932       return kStepScaleFactorTime;
6933     case FormControlType::InputMonth:
6934       return kStepScaleFactorMonth;
6935     case FormControlType::InputWeek:
6936       return kStepScaleFactorWeek;
6937     default:
6938       MOZ_ASSERT(false, "Unrecognized input type");
6939       return Decimal::nan();
6940   }
6941 }
6942 
GetDefaultStep() const6943 Decimal HTMLInputElement::GetDefaultStep() const {
6944   MOZ_ASSERT(DoesStepApply());
6945 
6946   switch (mType) {
6947     case FormControlType::InputDate:
6948     case FormControlType::InputMonth:
6949     case FormControlType::InputWeek:
6950     case FormControlType::InputNumber:
6951     case FormControlType::InputRange:
6952       return kDefaultStep;
6953     case FormControlType::InputTime:
6954     case FormControlType::InputDatetimeLocal:
6955       return kDefaultStepTime;
6956     default:
6957       MOZ_ASSERT(false, "Unrecognized input type");
6958       return Decimal::nan();
6959   }
6960 }
6961 
UpdateValidityUIBits(bool aIsFocused)6962 void HTMLInputElement::UpdateValidityUIBits(bool aIsFocused) {
6963   if (aIsFocused) {
6964     // If the invalid UI is shown, we should show it while focusing (and
6965     // update). Otherwise, we should not.
6966     mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
6967 
6968     // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
6969     // UI while typing.
6970     mCanShowValidUI = ShouldShowValidityUI();
6971   } else {
6972     mCanShowInvalidUI = true;
6973     mCanShowValidUI = true;
6974   }
6975 }
6976 
UpdateHasRange()6977 void HTMLInputElement::UpdateHasRange() {
6978   /*
6979    * There is a range if min/max applies for the type and if the element
6980    * currently have a valid min or max.
6981    */
6982 
6983   mHasRange = false;
6984 
6985   if (!DoesMinMaxApply()) {
6986     return;
6987   }
6988 
6989   Decimal minimum = GetMinimum();
6990   if (!minimum.isNaN()) {
6991     mHasRange = true;
6992     return;
6993   }
6994 
6995   Decimal maximum = GetMaximum();
6996   if (!maximum.isNaN()) {
6997     mHasRange = true;
6998     return;
6999   }
7000 }
7001 
PickerClosed()7002 void HTMLInputElement::PickerClosed() { mPickerRunning = false; }
7003 
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)7004 JSObject* HTMLInputElement::WrapNode(JSContext* aCx,
7005                                      JS::Handle<JSObject*> aGivenProto) {
7006   return HTMLInputElement_Binding::Wrap(aCx, this, aGivenProto);
7007 }
7008 
GetOrCreateGetFilesHelper(bool aRecursiveFlag,ErrorResult & aRv)7009 GetFilesHelper* HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag,
7010                                                             ErrorResult& aRv) {
7011   MOZ_ASSERT(mFileData);
7012 
7013   if (aRecursiveFlag) {
7014     if (!mFileData->mGetFilesRecursiveHelper) {
7015       mFileData->mGetFilesRecursiveHelper = GetFilesHelper::Create(
7016           GetFilesOrDirectoriesInternal(), aRecursiveFlag, aRv);
7017       if (NS_WARN_IF(aRv.Failed())) {
7018         return nullptr;
7019       }
7020     }
7021 
7022     return mFileData->mGetFilesRecursiveHelper;
7023   }
7024 
7025   if (!mFileData->mGetFilesNonRecursiveHelper) {
7026     mFileData->mGetFilesNonRecursiveHelper = GetFilesHelper::Create(
7027         GetFilesOrDirectoriesInternal(), aRecursiveFlag, aRv);
7028     if (NS_WARN_IF(aRv.Failed())) {
7029       return nullptr;
7030     }
7031   }
7032 
7033   return mFileData->mGetFilesNonRecursiveHelper;
7034 }
7035 
UpdateEntries(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories)7036 void HTMLInputElement::UpdateEntries(
7037     const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories) {
7038   MOZ_ASSERT(mFileData && mFileData->mEntries.IsEmpty());
7039 
7040   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
7041   MOZ_ASSERT(global);
7042 
7043   RefPtr<FileSystem> fs = FileSystem::Create(global);
7044   if (NS_WARN_IF(!fs)) {
7045     return;
7046   }
7047 
7048   Sequence<RefPtr<FileSystemEntry>> entries;
7049   for (uint32_t i = 0; i < aFilesOrDirectories.Length(); ++i) {
7050     RefPtr<FileSystemEntry> entry =
7051         FileSystemEntry::Create(global, aFilesOrDirectories[i], fs);
7052     MOZ_ASSERT(entry);
7053 
7054     if (!entries.AppendElement(entry, fallible)) {
7055       return;
7056     }
7057   }
7058 
7059   // The root fileSystem is a DirectoryEntry object that contains only the
7060   // dropped fileEntry and directoryEntry objects.
7061   fs->CreateRoot(entries);
7062 
7063   mFileData->mEntries = std::move(entries);
7064 }
7065 
GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>> & aSequence)7066 void HTMLInputElement::GetWebkitEntries(
7067     nsTArray<RefPtr<FileSystemEntry>>& aSequence) {
7068   if (NS_WARN_IF(mType != FormControlType::InputFile)) {
7069     return;
7070   }
7071 
7072   Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED, true);
7073   aSequence.AppendElements(mFileData->mEntries);
7074 }
7075 
GetLabels()7076 already_AddRefed<nsINodeList> HTMLInputElement::GetLabels() {
7077   if (!IsLabelable()) {
7078     return nullptr;
7079   }
7080 
7081   return nsGenericHTMLElement::Labels();
7082 }
7083 
MaybeFireInputPasswordRemoved()7084 void HTMLInputElement::MaybeFireInputPasswordRemoved() {
7085   // We want this event to be fired only when the password field is removed
7086   // from the DOM tree, not when it is released (ex, tab is closed). So don't
7087   // fire an event when the password input field doesn't have a docshell.
7088   Document* doc = GetComposedDoc();
7089   nsIDocShell* container = doc ? doc->GetDocShell() : nullptr;
7090   if (!container) {
7091     return;
7092   }
7093 
7094   // Right now, only the password manager listens to the event and only listen
7095   // to it under certain circumstances. So don't fire this event unless
7096   // necessary.
7097   if (!doc->ShouldNotifyFormOrPasswordRemoved()) {
7098     return;
7099   }
7100 
7101   RefPtr<AsyncEventDispatcher> asyncDispatcher =
7102       new AsyncEventDispatcher(this, u"DOMInputPasswordRemoved"_ns,
7103                                CanBubble::eNo, ChromeOnlyDispatch::eYes);
7104   asyncDispatcher->RunDOMEventWhenSafe();
7105 }
7106 
7107 }  // namespace mozilla::dom
7108 
7109 #undef NS_ORIGINAL_CHECKED_VALUE
7110