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/DebugOnly.h"
12 #include "mozilla/dom/Date.h"
13 #include "mozilla/dom/Directory.h"
14 #include "mozilla/dom/HTMLFormSubmission.h"
15 #include "mozilla/dom/FileSystemUtils.h"
16 #include "mozilla/dom/GetFilesHelper.h"
17 #include "nsAttrValueInlines.h"
18 #include "nsCRTGlue.h"
19 
20 #include "nsIDOMHTMLInputElement.h"
21 #include "nsITextControlElement.h"
22 #include "nsIDOMNSEditableElement.h"
23 #include "nsIRadioVisitor.h"
24 #include "nsIPhonetic.h"
25 
26 #include "HTMLFormSubmissionConstants.h"
27 #include "mozilla/Telemetry.h"
28 #include "nsIControllers.h"
29 #include "nsIStringBundle.h"
30 #include "nsFocusManager.h"
31 #include "nsColorControlFrame.h"
32 #include "nsNumberControlFrame.h"
33 #include "nsPIDOMWindow.h"
34 #include "nsRepeatService.h"
35 #include "nsContentCID.h"
36 #include "nsIComponentManager.h"
37 #include "nsIDOMHTMLFormElement.h"
38 #include "mozilla/dom/ProgressEvent.h"
39 #include "nsGkAtoms.h"
40 #include "nsStyleConsts.h"
41 #include "nsPresContext.h"
42 #include "nsMappedAttributes.h"
43 #include "nsIFormControl.h"
44 #include "nsIDocument.h"
45 #include "nsIPresShell.h"
46 #include "nsIFormControlFrame.h"
47 #include "nsITextControlFrame.h"
48 #include "nsIFrame.h"
49 #include "nsRangeFrame.h"
50 #include "nsIServiceManager.h"
51 #include "nsError.h"
52 #include "nsIEditor.h"
53 #include "nsIIOService.h"
54 #include "nsDocument.h"
55 #include "nsAttrValueOrString.h"
56 #include "nsDateTimeControlFrame.h"
57 
58 #include "nsPresState.h"
59 #include "nsIDOMEvent.h"
60 #include "nsIDOMNodeList.h"
61 #include "nsIDOMHTMLCollection.h"
62 #include "nsLinebreakConverter.h" //to strip out carriage returns
63 #include "nsReadableUtils.h"
64 #include "nsUnicharUtils.h"
65 #include "nsLayoutUtils.h"
66 #include "nsVariant.h"
67 
68 #include "nsIDOMMutationEvent.h"
69 #include "mozilla/ContentEvents.h"
70 #include "mozilla/EventDispatcher.h"
71 #include "mozilla/EventStates.h"
72 #include "mozilla/InternalMutationEvent.h"
73 #include "mozilla/TextEvents.h"
74 #include "mozilla/TouchEvents.h"
75 
76 #include "nsRuleData.h"
77 #include <algorithm>
78 
79 // input type=radio
80 #include "nsIRadioGroupContainer.h"
81 
82 // input type=file
83 #include "mozilla/dom/FileSystemEntry.h"
84 #include "mozilla/dom/FileSystem.h"
85 #include "mozilla/dom/File.h"
86 #include "mozilla/dom/FileList.h"
87 #include "nsIFile.h"
88 #include "nsNetCID.h"
89 #include "nsNetUtil.h"
90 #include "nsDirectoryServiceDefs.h"
91 #include "nsIContentPrefService.h"
92 #include "nsIMIMEService.h"
93 #include "nsIObserverService.h"
94 #include "nsIPopupWindowManager.h"
95 #include "nsGlobalWindow.h"
96 
97 // input type=image
98 #include "nsImageLoadingContent.h"
99 #include "imgRequestProxy.h"
100 
101 #include "mozAutoDocUpdate.h"
102 #include "nsContentCreatorFunctions.h"
103 #include "nsContentUtils.h"
104 #include "mozilla/dom/DirectionalityUtils.h"
105 #include "nsRadioVisitor.h"
106 #include "nsTextEditorState.h"
107 
108 #include "mozilla/LookAndFeel.h"
109 #include "mozilla/Preferences.h"
110 #include "mozilla/MathAlgorithms.h"
111 
112 #include "nsIIDNService.h"
113 
114 #include <limits>
115 
116 #include "nsIColorPicker.h"
117 #include "nsIDatePicker.h"
118 #include "nsIStringEnumerator.h"
119 #include "HTMLSplitOnSpacesTokenizer.h"
120 #include "nsIController.h"
121 #include "nsIMIMEInfo.h"
122 #include "nsFrameSelection.h"
123 
124 #include "nsIConsoleService.h"
125 
126 // input type=date
127 #include "js/Date.h"
128 
129 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
130 
131 // XXX align=left, hspace, vspace, border? other nav4 attrs
132 
133 static NS_DEFINE_CID(kXULControllersCID,  NS_XULCONTROLLERS_CID);
134 
135 // This must come outside of any namespace, or else it won't overload with the
136 // double based version in nsMathUtils.h
137 inline mozilla::Decimal
NS_floorModulo(mozilla::Decimal x,mozilla::Decimal y)138 NS_floorModulo(mozilla::Decimal x, mozilla::Decimal y)
139 {
140   return (x - y * (x / y).floor());
141 }
142 
143 namespace mozilla {
144 namespace dom {
145 
146 // First bits are needed for the control type.
147 #define NS_OUTER_ACTIVATE_EVENT   (1 << 9)
148 #define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
149 #define NS_NO_CONTENT_DISPATCH    (1 << 11)
150 #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
151 #define NS_CONTROL_TYPE(bits)  ((bits) & ~( \
152   NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | NS_NO_CONTENT_DISPATCH | \
153   NS_ORIGINAL_INDETERMINATE_VALUE))
154 
155 // whether textfields should be selected once focused:
156 //  -1: no, 1: yes, 0: uninitialized
157 static int32_t gSelectTextFieldOnFocus;
158 UploadLastDir* HTMLInputElement::gUploadLastDir;
159 
160 static const nsAttrValue::EnumTable kInputTypeTable[] = {
161   { "button", NS_FORM_INPUT_BUTTON },
162   { "checkbox", NS_FORM_INPUT_CHECKBOX },
163   { "color", NS_FORM_INPUT_COLOR },
164   { "date", NS_FORM_INPUT_DATE },
165   { "datetime-local", NS_FORM_INPUT_DATETIME_LOCAL },
166   { "email", NS_FORM_INPUT_EMAIL },
167   { "file", NS_FORM_INPUT_FILE },
168   { "hidden", NS_FORM_INPUT_HIDDEN },
169   { "reset", NS_FORM_INPUT_RESET },
170   { "image", NS_FORM_INPUT_IMAGE },
171   { "month", NS_FORM_INPUT_MONTH },
172   { "number", NS_FORM_INPUT_NUMBER },
173   { "password", NS_FORM_INPUT_PASSWORD },
174   { "radio", NS_FORM_INPUT_RADIO },
175   { "range", NS_FORM_INPUT_RANGE },
176   { "search", NS_FORM_INPUT_SEARCH },
177   { "submit", NS_FORM_INPUT_SUBMIT },
178   { "tel", NS_FORM_INPUT_TEL },
179   { "text", NS_FORM_INPUT_TEXT },
180   { "time", NS_FORM_INPUT_TIME },
181   { "url", NS_FORM_INPUT_URL },
182   { "week", NS_FORM_INPUT_WEEK },
183   { nullptr, 0 }
184 };
185 
186 // Default type is 'text'.
187 static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[18];
188 
189 static const uint8_t NS_INPUT_INPUTMODE_AUTO              = 0;
190 static const uint8_t NS_INPUT_INPUTMODE_NUMERIC           = 1;
191 static const uint8_t NS_INPUT_INPUTMODE_DIGIT             = 2;
192 static const uint8_t NS_INPUT_INPUTMODE_UPPERCASE         = 3;
193 static const uint8_t NS_INPUT_INPUTMODE_LOWERCASE         = 4;
194 static const uint8_t NS_INPUT_INPUTMODE_TITLECASE         = 5;
195 static const uint8_t NS_INPUT_INPUTMODE_AUTOCAPITALIZED   = 6;
196 
197 static const nsAttrValue::EnumTable kInputInputmodeTable[] = {
198   { "auto", NS_INPUT_INPUTMODE_AUTO },
199   { "numeric", NS_INPUT_INPUTMODE_NUMERIC },
200   { "digit", NS_INPUT_INPUTMODE_DIGIT },
201   { "uppercase", NS_INPUT_INPUTMODE_UPPERCASE },
202   { "lowercase", NS_INPUT_INPUTMODE_LOWERCASE },
203   { "titlecase", NS_INPUT_INPUTMODE_TITLECASE },
204   { "autocapitalized", NS_INPUT_INPUTMODE_AUTOCAPITALIZED },
205   { nullptr, 0 }
206 };
207 
208 // Default inputmode value is "auto".
209 static const nsAttrValue::EnumTable* kInputDefaultInputmode = &kInputInputmodeTable[0];
210 
211 const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000);
212 const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
213 const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
214 const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1);
215 const Decimal HTMLInputElement::kStepScaleFactorWeek = Decimal(7 * 86400000);
216 const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
217 const Decimal HTMLInputElement::kDefaultStepBaseWeek = Decimal(-259200000);
218 const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
219 const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
220 const Decimal HTMLInputElement::kStepAny = Decimal(0);
221 
222 const double HTMLInputElement::kMinimumYear = 1;
223 const double HTMLInputElement::kMaximumYear = 275760;
224 const double HTMLInputElement::kMaximumWeekInMaximumYear = 37;
225 const double HTMLInputElement::kMaximumDayInMaximumYear = 13;
226 const double HTMLInputElement::kMaximumMonthInMaximumYear = 9;
227 const double HTMLInputElement::kMaximumWeekInYear = 53;
228 const double HTMLInputElement::kMsPerDay = 24 * 60 * 60 * 1000;
229 
230 #define NS_INPUT_ELEMENT_STATE_IID                 \
231 { /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */       \
232   0xdc3b3d14,                                      \
233   0x23e2,                                          \
234   0x4479,                                          \
235   {0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
236 }
237 
238 #define PROGRESS_STR "progress"
239 static const uint32_t kProgressEventInterval = 50; // ms
240 
241 // An helper class for the dispatching of the 'change' event.
242 // This class is used when the FilePicker finished its task (or when files and
243 // directories are set by some chrome/test only method).
244 // The task of this class is to postpone the dispatching of 'change' and 'input'
245 // events at the end of the exploration of the directories.
246 class DispatchChangeEventCallback final : public GetFilesCallback
247 {
248 public:
DispatchChangeEventCallback(HTMLInputElement * aInputElement)249   explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
250     : mInputElement(aInputElement)
251   {
252     MOZ_ASSERT(aInputElement);
253   }
254 
255   virtual void
Callback(nsresult aStatus,const Sequence<RefPtr<File>> & aFiles)256   Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override
257   {
258     nsTArray<OwningFileOrDirectory> array;
259     for (uint32_t i = 0; i < aFiles.Length(); ++i) {
260       OwningFileOrDirectory* element = array.AppendElement();
261       element->SetAsFile() = aFiles[i];
262     }
263 
264     mInputElement->SetFilesOrDirectories(array, true);
265     Unused << NS_WARN_IF(NS_FAILED(DispatchEvents()));
266   }
267 
268   nsresult
DispatchEvents()269   DispatchEvents()
270   {
271     nsresult rv = NS_OK;
272     rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
273                                               static_cast<nsIDOMHTMLInputElement*>(mInputElement.get()),
274                                               NS_LITERAL_STRING("input"), true,
275                                               false);
276     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
277 
278     rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
279                                               static_cast<nsIDOMHTMLInputElement*>(mInputElement.get()),
280                                               NS_LITERAL_STRING("change"), true,
281                                               false);
282 
283     return rv;
284   }
285 
286 private:
287   RefPtr<HTMLInputElement> mInputElement;
288 };
289 
290 class HTMLInputElementState final : public nsISupports
291 {
292   public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)293     NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)
294     NS_DECL_ISUPPORTS
295 
296     bool IsCheckedSet()
297     {
298       return mCheckedSet;
299     }
300 
GetChecked()301     bool GetChecked()
302     {
303       return mChecked;
304     }
305 
SetChecked(bool aChecked)306     void SetChecked(bool aChecked)
307     {
308       mChecked = aChecked;
309       mCheckedSet = true;
310     }
311 
GetValue()312     const nsString& GetValue()
313     {
314       return mValue;
315     }
316 
SetValue(const nsAString & aValue)317     void SetValue(const nsAString& aValue)
318     {
319       mValue = aValue;
320     }
321 
322     void
GetFilesOrDirectories(nsPIDOMWindowInner * aWindow,nsTArray<OwningFileOrDirectory> & aResult) const323     GetFilesOrDirectories(nsPIDOMWindowInner* aWindow,
324                           nsTArray<OwningFileOrDirectory>& aResult) const
325     {
326       for (uint32_t i = 0; i < mBlobImplsOrDirectoryPaths.Length(); ++i) {
327         if (mBlobImplsOrDirectoryPaths[i].mType == BlobImplOrDirectoryPath::eBlobImpl) {
328           RefPtr<File> file =
329             File::Create(aWindow,
330                          mBlobImplsOrDirectoryPaths[i].mBlobImpl);
331           MOZ_ASSERT(file);
332 
333           OwningFileOrDirectory* element = aResult.AppendElement();
334           element->SetAsFile() = file;
335         } else {
336           MOZ_ASSERT(mBlobImplsOrDirectoryPaths[i].mType == BlobImplOrDirectoryPath::eDirectoryPath);
337 
338           nsCOMPtr<nsIFile> file;
339           nsresult rv =
340             NS_NewLocalFile(mBlobImplsOrDirectoryPaths[i].mDirectoryPath,
341                             true, getter_AddRefs(file));
342           if (NS_WARN_IF(NS_FAILED(rv))) {
343             continue;
344           }
345 
346           RefPtr<Directory> directory = Directory::Create(aWindow, file);
347           MOZ_ASSERT(directory);
348 
349           OwningFileOrDirectory* element = aResult.AppendElement();
350           element->SetAsDirectory() = directory;
351         }
352       }
353     }
354 
SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory> & aArray)355     void SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aArray)
356     {
357       mBlobImplsOrDirectoryPaths.Clear();
358       for (uint32_t i = 0; i < aArray.Length(); ++i) {
359         if (aArray[i].IsFile()) {
360           BlobImplOrDirectoryPath* data = mBlobImplsOrDirectoryPaths.AppendElement();
361 
362           data->mBlobImpl = aArray[i].GetAsFile()->Impl();
363           data->mType = BlobImplOrDirectoryPath::eBlobImpl;
364         } else {
365           MOZ_ASSERT(aArray[i].IsDirectory());
366           nsAutoString fullPath;
367           nsresult rv = aArray[i].GetAsDirectory()->GetFullRealPath(fullPath);
368           if (NS_WARN_IF(NS_FAILED(rv))) {
369             continue;
370           }
371 
372           BlobImplOrDirectoryPath* data =
373             mBlobImplsOrDirectoryPaths.AppendElement();
374 
375           data->mDirectoryPath = fullPath;
376           data->mType = BlobImplOrDirectoryPath::eDirectoryPath;
377         }
378       }
379     }
380 
HTMLInputElementState()381     HTMLInputElementState()
382       : mValue()
383       , mChecked(false)
384       , mCheckedSet(false)
385     {}
386 
387   protected:
~HTMLInputElementState()388     ~HTMLInputElementState() {}
389 
390     nsString mValue;
391 
392     struct BlobImplOrDirectoryPath
393     {
394       RefPtr<BlobImpl> mBlobImpl;
395       nsString mDirectoryPath;
396 
397       enum {
398         eBlobImpl,
399         eDirectoryPath
400       } mType;
401     };
402 
403     nsTArray<BlobImplOrDirectoryPath> mBlobImplsOrDirectoryPaths;
404 
405     bool mChecked;
406     bool mCheckedSet;
407 };
408 
NS_DEFINE_STATIC_IID_ACCESSOR(HTMLInputElementState,NS_INPUT_ELEMENT_STATE_IID)409 NS_DEFINE_STATIC_IID_ACCESSOR(HTMLInputElementState, NS_INPUT_ELEMENT_STATE_IID)
410 
411 NS_IMPL_ISUPPORTS(HTMLInputElementState, HTMLInputElementState)
412 
413 HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
414   HTMLInputElement* aInput, nsIFilePicker* aFilePicker)
415   : mFilePicker(aFilePicker)
416   , mInput(aInput)
417 {
418 }
419 
NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback,nsIContentPrefCallback2)420 NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2)
421 
422 NS_IMETHODIMP
423 UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason)
424 {
425   nsCOMPtr<nsIFile> localFile;
426   nsAutoString prefStr;
427 
428   if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) {
429     prefStr = Preferences::GetString("dom.input.fallbackUploadDir");
430     if (prefStr.IsEmpty()) {
431       // If no custom directory was set through the pref, default to
432       // "desktop" directory for each platform.
433       NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(localFile));
434     }
435   }
436 
437   if (!localFile) {
438     if (prefStr.IsEmpty() && mResult) {
439       nsCOMPtr<nsIVariant> pref;
440       mResult->GetValue(getter_AddRefs(pref));
441       pref->GetAsAString(prefStr);
442     }
443     localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
444     localFile->InitWithPath(prefStr);
445   }
446 
447   mFilePicker->SetDisplayDirectory(localFile);
448   mFilePicker->Open(mFpCallback);
449   return NS_OK;
450 }
451 
452 NS_IMETHODIMP
HandleResult(nsIContentPref * pref)453 UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref* pref)
454 {
455   mResult = pref;
456   return NS_OK;
457 }
458 
459 NS_IMETHODIMP
HandleError(nsresult error)460 UploadLastDir::ContentPrefCallback::HandleError(nsresult error)
461 {
462   // HandleCompletion is always called (even with HandleError was called),
463   // so we don't need to do anything special here.
464   return NS_OK;
465 }
466 
467 namespace {
468 
469 /**
470  * This may return nullptr if the DOM File's implementation of
471  * File::mozFullPathInternal does not successfully return a non-empty
472  * string that is a valid path. This can happen on Firefox OS, for example,
473  * where the file picker can create Blobs.
474  */
475 static already_AddRefed<nsIFile>
LastUsedDirectory(const OwningFileOrDirectory & aData)476 LastUsedDirectory(const OwningFileOrDirectory& aData)
477 {
478   if (aData.IsFile()) {
479     nsAutoString path;
480     ErrorResult error;
481     aData.GetAsFile()->GetMozFullPathInternal(path, error);
482     if (error.Failed() || path.IsEmpty()) {
483       error.SuppressException();
484       return nullptr;
485     }
486 
487     nsCOMPtr<nsIFile> localFile;
488     nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
489                                         getter_AddRefs(localFile));
490     if (NS_WARN_IF(NS_FAILED(rv))) {
491       return nullptr;
492     }
493 
494     nsCOMPtr<nsIFile> parentFile;
495     rv = localFile->GetParent(getter_AddRefs(parentFile));
496     if (NS_WARN_IF(NS_FAILED(rv))) {
497       return nullptr;
498     }
499 
500     return parentFile.forget();
501   }
502 
503   MOZ_ASSERT(aData.IsDirectory());
504 
505   nsCOMPtr<nsIFile> localFile = aData.GetAsDirectory()->GetInternalNsIFile();
506   MOZ_ASSERT(localFile);
507 
508   return localFile.forget();
509 }
510 
511 void
GetDOMFileOrDirectoryName(const OwningFileOrDirectory & aData,nsAString & aName)512 GetDOMFileOrDirectoryName(const OwningFileOrDirectory& aData,
513                           nsAString& aName)
514 {
515   if (aData.IsFile()) {
516     aData.GetAsFile()->GetName(aName);
517   } else {
518     MOZ_ASSERT(aData.IsDirectory());
519     ErrorResult rv;
520     aData.GetAsDirectory()->GetName(aName, rv);
521     if (NS_WARN_IF(rv.Failed())) {
522       rv.SuppressException();
523     }
524   }
525 }
526 
527 void
GetDOMFileOrDirectoryPath(const OwningFileOrDirectory & aData,nsAString & aPath,ErrorResult & aRv)528 GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
529                           nsAString& aPath,
530                           ErrorResult& aRv)
531 {
532   if (aData.IsFile()) {
533     aData.GetAsFile()->GetMozFullPathInternal(aPath, aRv);
534   } else {
535     MOZ_ASSERT(aData.IsDirectory());
536     aData.GetAsDirectory()->GetFullRealPath(aPath);
537   }
538 }
539 
540 } // namespace
541 
542 /* static */
543 bool
ValueAsDateEnabled(JSContext * cx,JSObject * obj)544 HTMLInputElement::ValueAsDateEnabled(JSContext* cx, JSObject* obj)
545 {
546   return Preferences::GetBool("dom.experimental_forms", false) ||
547     Preferences::GetBool("dom.forms.datepicker", false) ||
548     Preferences::GetBool("dom.forms.datetime", false);
549 }
550 
551 NS_IMETHODIMP
Done(int16_t aResult)552 HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult)
553 {
554   mInput->PickerClosed();
555 
556   if (aResult == nsIFilePicker::returnCancel) {
557     return NS_OK;
558   }
559 
560   int16_t mode;
561   mFilePicker->GetMode(&mode);
562 
563   // Collect new selected filenames
564   nsTArray<OwningFileOrDirectory> newFilesOrDirectories;
565   if (mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple)) {
566     nsCOMPtr<nsISimpleEnumerator> iter;
567     nsresult rv =
568       mFilePicker->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter));
569     NS_ENSURE_SUCCESS(rv, rv);
570 
571     if (!iter) {
572       return NS_OK;
573     }
574 
575     nsCOMPtr<nsISupports> tmp;
576     bool hasMore = true;
577 
578     while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
579       iter->GetNext(getter_AddRefs(tmp));
580       nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(tmp);
581       MOZ_ASSERT(domBlob,
582                  "Null file object from FilePicker's file enumerator?");
583       if (!domBlob) {
584         continue;
585       }
586 
587       OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
588       element->SetAsFile() = static_cast<File*>(domBlob.get());
589     }
590   } else {
591     MOZ_ASSERT(mode == static_cast<int16_t>(nsIFilePicker::modeOpen) ||
592                mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder));
593     nsCOMPtr<nsISupports> tmp;
594     nsresult rv = mFilePicker->GetDomFileOrDirectory(getter_AddRefs(tmp));
595     NS_ENSURE_SUCCESS(rv, rv);
596 
597     nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(tmp);
598     if (blob) {
599       RefPtr<File> file = static_cast<Blob*>(blob.get())->ToFile();
600       MOZ_ASSERT(file);
601 
602       OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
603       element->SetAsFile() = file;
604     } else if (tmp) {
605       RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
606       OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
607       element->SetAsDirectory() = directory;
608     }
609   }
610 
611   if (newFilesOrDirectories.IsEmpty()) {
612     return NS_OK;
613   }
614 
615   // Store the last used directory using the content pref service:
616   nsCOMPtr<nsIFile> lastUsedDir = LastUsedDirectory(newFilesOrDirectories[0]);
617 
618   if (lastUsedDir) {
619     HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
620       mInput->OwnerDoc(), lastUsedDir);
621   }
622 
623   // The text control frame (if there is one) isn't going to send a change
624   // event because it will think this is done by a script.
625   // So, we can safely send one by ourself.
626   mInput->SetFilesOrDirectories(newFilesOrDirectories, true);
627 
628   RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
629     new DispatchChangeEventCallback(mInput);
630 
631   if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
632       mInput->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
633     ErrorResult error;
634     GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
635     if (NS_WARN_IF(error.Failed())) {
636       return error.StealNSResult();
637     }
638 
639     helper->AddCallback(dispatchChangeEventCallback);
640     return NS_OK;
641   }
642 
643   return dispatchChangeEventCallback->DispatchEvents();
644 }
645 
646 NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback,
647                   nsIFilePickerShownCallback)
648 
649 class nsColorPickerShownCallback final
650   : public nsIColorPickerShownCallback
651 {
~nsColorPickerShownCallback()652   ~nsColorPickerShownCallback() {}
653 
654 public:
nsColorPickerShownCallback(HTMLInputElement * aInput,nsIColorPicker * aColorPicker)655   nsColorPickerShownCallback(HTMLInputElement* aInput,
656                              nsIColorPicker* aColorPicker)
657     : mInput(aInput)
658     , mColorPicker(aColorPicker)
659     , mValueChanged(false)
660   {}
661 
662   NS_DECL_ISUPPORTS
663 
664   NS_IMETHOD Update(const nsAString& aColor) override;
665   NS_IMETHOD Done(const nsAString& aColor) override;
666 
667 private:
668   /**
669    * Updates the internals of the object using aColor as the new value.
670    * If aTrustedUpdate is true, it will consider that aColor is a new value.
671    * Otherwise, it will check that aColor is different from the current value.
672    */
673   nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
674 
675   RefPtr<HTMLInputElement> mInput;
676   nsCOMPtr<nsIColorPicker>   mColorPicker;
677   bool                       mValueChanged;
678 };
679 
680 nsresult
UpdateInternal(const nsAString & aColor,bool aTrustedUpdate)681 nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
682                                            bool aTrustedUpdate)
683 {
684   bool valueChanged = false;
685 
686   nsAutoString oldValue;
687   if (aTrustedUpdate) {
688     valueChanged = true;
689   } else {
690     mInput->GetValue(oldValue);
691   }
692 
693   mInput->SetValue(aColor);
694 
695   if (!aTrustedUpdate) {
696     nsAutoString newValue;
697     mInput->GetValue(newValue);
698     if (!oldValue.Equals(newValue)) {
699       valueChanged = true;
700     }
701   }
702 
703   if (valueChanged) {
704     mValueChanged = true;
705     return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
706                                                 static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
707                                                 NS_LITERAL_STRING("input"), true,
708                                                 false);
709   }
710 
711   return NS_OK;
712 }
713 
714 NS_IMETHODIMP
Update(const nsAString & aColor)715 nsColorPickerShownCallback::Update(const nsAString& aColor)
716 {
717   return UpdateInternal(aColor, true);
718 }
719 
720 NS_IMETHODIMP
Done(const nsAString & aColor)721 nsColorPickerShownCallback::Done(const nsAString& aColor)
722 {
723   /**
724    * When Done() is called, we might be at the end of a serie of Update() calls
725    * in which case mValueChanged is set to true and a change event will have to
726    * be fired but we might also be in a one shot Done() call situation in which
727    * case we should fire a change event iif the value actually changed.
728    * UpdateInternal(bool) is taking care of that logic for us.
729    */
730   nsresult rv = NS_OK;
731 
732   mInput->PickerClosed();
733 
734   if (!aColor.IsEmpty()) {
735     UpdateInternal(aColor, false);
736   }
737 
738   if (mValueChanged) {
739     rv = nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
740                                               static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
741                                               NS_LITERAL_STRING("change"), true,
742                                               false);
743   }
744 
745   return rv;
746 }
747 
748 NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback)
749 
750 class DatePickerShownCallback final : public nsIDatePickerShownCallback
751 {
~DatePickerShownCallback()752   ~DatePickerShownCallback() {}
753 public:
DatePickerShownCallback(HTMLInputElement * aInput,nsIDatePicker * aDatePicker)754   DatePickerShownCallback(HTMLInputElement* aInput,
755                           nsIDatePicker* aDatePicker)
756     : mInput(aInput)
757     , mDatePicker(aDatePicker)
758   {}
759 
760   NS_DECL_ISUPPORTS
761 
762   NS_IMETHOD Done(const nsAString& aDate) override;
763   NS_IMETHOD Cancel() override;
764 
765 private:
766   RefPtr<HTMLInputElement> mInput;
767   nsCOMPtr<nsIDatePicker> mDatePicker;
768 };
769 
770 NS_IMETHODIMP
Cancel()771 DatePickerShownCallback::Cancel()
772 {
773   mInput->PickerClosed();
774   return NS_OK;
775 }
776 
777 NS_IMETHODIMP
Done(const nsAString & aDate)778 DatePickerShownCallback::Done(const nsAString& aDate)
779 {
780   nsAutoString oldValue;
781 
782   mInput->PickerClosed();
783   mInput->GetValue(oldValue);
784 
785   if(!oldValue.Equals(aDate)){
786     mInput->SetValue(aDate);
787     nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
788                             static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
789                             NS_LITERAL_STRING("input"), true,
790                             false);
791     return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
792                             static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
793                             NS_LITERAL_STRING("change"), true,
794                             false);
795   }
796 
797   return NS_OK;
798 }
799 
NS_IMPL_ISUPPORTS(DatePickerShownCallback,nsIDatePickerShownCallback)800 NS_IMPL_ISUPPORTS(DatePickerShownCallback, nsIDatePickerShownCallback)
801 
802 
803 bool
804 HTMLInputElement::IsPopupBlocked() const
805 {
806   nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
807   MOZ_ASSERT(win, "window should not be null");
808   if (!win) {
809     return true;
810   }
811 
812   // Check if page is allowed to open the popup
813   if (win->GetPopupControlState() <= openControlled) {
814     return false;
815   }
816 
817   nsCOMPtr<nsIPopupWindowManager> pm = do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID);
818   if (!pm) {
819     return true;
820   }
821 
822   uint32_t permission;
823   pm->TestPermission(OwnerDoc()->NodePrincipal(), &permission);
824   return permission == nsIPopupWindowManager::DENY_POPUP;
825 }
826 
827 nsresult
InitDatePicker()828 HTMLInputElement::InitDatePicker()
829 {
830   if (!Preferences::GetBool("dom.forms.datepicker", false)) {
831     return NS_OK;
832   }
833 
834   if (mPickerRunning) {
835     NS_WARNING("Just one nsIDatePicker is allowed");
836     return NS_ERROR_FAILURE;
837   }
838 
839   nsCOMPtr<nsIDocument> doc = OwnerDoc();
840 
841   nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
842   if (!win) {
843     return NS_ERROR_FAILURE;
844   }
845 
846   if (IsPopupBlocked()) {
847     win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
848     return NS_OK;
849   }
850 
851   // Get Loc title
852   nsXPIDLString title;
853   nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
854                                      "DatePicker", title);
855 
856   nsresult rv;
857   nsCOMPtr<nsIDatePicker> datePicker = do_CreateInstance("@mozilla.org/datepicker;1", &rv);
858   if (!datePicker) {
859     return rv;
860   }
861 
862   nsAutoString initialValue;
863   GetValueInternal(initialValue);
864   rv = datePicker->Init(win, title, initialValue);
865 
866   nsCOMPtr<nsIDatePickerShownCallback> callback =
867     new DatePickerShownCallback(this, datePicker);
868 
869   rv = datePicker->Open(callback);
870   if (NS_SUCCEEDED(rv)) {
871     mPickerRunning = true;
872   }
873 
874   return rv;
875 }
876 
877 nsresult
InitColorPicker()878 HTMLInputElement::InitColorPicker()
879 {
880   if (mPickerRunning) {
881     NS_WARNING("Just one nsIColorPicker is allowed");
882     return NS_ERROR_FAILURE;
883   }
884 
885   nsCOMPtr<nsIDocument> doc = OwnerDoc();
886 
887   nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
888   if (!win) {
889     return NS_ERROR_FAILURE;
890   }
891 
892   if (IsPopupBlocked()) {
893     win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
894     return NS_OK;
895   }
896 
897   // Get Loc title
898   nsXPIDLString title;
899   nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
900                                      "ColorPicker", title);
901 
902   nsCOMPtr<nsIColorPicker> colorPicker = do_CreateInstance("@mozilla.org/colorpicker;1");
903   if (!colorPicker) {
904     return NS_ERROR_FAILURE;
905   }
906 
907   nsAutoString initialValue;
908   GetValueInternal(initialValue);
909   nsresult rv = colorPicker->Init(win, title, initialValue);
910   NS_ENSURE_SUCCESS(rv, rv);
911 
912   nsCOMPtr<nsIColorPickerShownCallback> callback =
913     new nsColorPickerShownCallback(this, colorPicker);
914 
915   rv = colorPicker->Open(callback);
916   if (NS_SUCCEEDED(rv)) {
917     mPickerRunning = true;
918   }
919 
920   return rv;
921 }
922 
923 nsresult
InitFilePicker(FilePickerType aType)924 HTMLInputElement::InitFilePicker(FilePickerType aType)
925 {
926   if (mPickerRunning) {
927     NS_WARNING("Just one nsIFilePicker is allowed");
928     return NS_ERROR_FAILURE;
929   }
930 
931   // Get parent nsPIDOMWindow object.
932   nsCOMPtr<nsIDocument> doc = OwnerDoc();
933 
934   nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
935   if (!win) {
936     return NS_ERROR_FAILURE;
937   }
938 
939   if (IsPopupBlocked()) {
940     win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
941     return NS_OK;
942   }
943 
944   // Get Loc title
945   nsXPIDLString title;
946   nsXPIDLString okButtonLabel;
947   if (aType == FILE_PICKER_DIRECTORY) {
948     nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
949                                        "DirectoryUpload", title);
950 
951     nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
952                                        "DirectoryPickerOkButtonLabel",
953                                        okButtonLabel);
954   } else {
955     nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
956                                        "FileUpload", title);
957   }
958 
959   nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1");
960   if (!filePicker)
961     return NS_ERROR_FAILURE;
962 
963   int16_t mode;
964 
965   if (aType == FILE_PICKER_DIRECTORY) {
966     mode = static_cast<int16_t>(nsIFilePicker::modeGetFolder);
967   } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
968     mode = static_cast<int16_t>(nsIFilePicker::modeOpenMultiple);
969   } else {
970     mode = static_cast<int16_t>(nsIFilePicker::modeOpen);
971   }
972 
973   nsresult rv = filePicker->Init(win, title, mode);
974   NS_ENSURE_SUCCESS(rv, rv);
975 
976   if (!okButtonLabel.IsEmpty()) {
977     filePicker->SetOkButtonLabel(okButtonLabel);
978   }
979 
980   // Native directory pickers ignore file type filters, so we don't spend
981   // cycles adding them for FILE_PICKER_DIRECTORY.
982   if (HasAttr(kNameSpaceID_None, nsGkAtoms::accept) &&
983       aType != FILE_PICKER_DIRECTORY) {
984     SetFilePickerFiltersFromAccept(filePicker);
985   } else {
986     filePicker->AppendFilters(nsIFilePicker::filterAll);
987   }
988 
989   // Set default directory and filename
990   nsAutoString defaultName;
991 
992   const nsTArray<OwningFileOrDirectory>& oldFiles =
993     GetFilesOrDirectoriesInternal();
994 
995   nsCOMPtr<nsIFilePickerShownCallback> callback =
996     new HTMLInputElement::nsFilePickerShownCallback(this, filePicker);
997 
998   if (!oldFiles.IsEmpty() &&
999       aType != FILE_PICKER_DIRECTORY) {
1000     nsAutoString path;
1001 
1002     nsCOMPtr<nsIFile> parentFile = LastUsedDirectory(oldFiles[0]);
1003     if (parentFile) {
1004       filePicker->SetDisplayDirectory(parentFile);
1005     }
1006 
1007     // Unfortunately nsIFilePicker doesn't allow multiple files to be
1008     // default-selected, so only select something by default if exactly
1009     // one file was selected before.
1010     if (oldFiles.Length() == 1) {
1011       nsAutoString leafName;
1012       GetDOMFileOrDirectoryName(oldFiles[0], leafName);
1013 
1014       if (!leafName.IsEmpty()) {
1015         filePicker->SetDefaultString(leafName);
1016       }
1017     }
1018 
1019     rv = filePicker->Open(callback);
1020     if (NS_SUCCEEDED(rv)) {
1021       mPickerRunning = true;
1022     }
1023 
1024     return rv;
1025   }
1026 
1027   HTMLInputElement::gUploadLastDir->FetchDirectoryAndDisplayPicker(doc, filePicker, callback);
1028   mPickerRunning = true;
1029   return NS_OK;
1030 }
1031 
1032 #define CPS_PREF_NAME NS_LITERAL_STRING("browser.upload.lastDir")
1033 
NS_IMPL_ISUPPORTS(UploadLastDir,nsIObserver,nsISupportsWeakReference)1034 NS_IMPL_ISUPPORTS(UploadLastDir, nsIObserver, nsISupportsWeakReference)
1035 
1036 void
1037 HTMLInputElement::InitUploadLastDir() {
1038   gUploadLastDir = new UploadLastDir();
1039   NS_ADDREF(gUploadLastDir);
1040 
1041   nsCOMPtr<nsIObserverService> observerService =
1042     mozilla::services::GetObserverService();
1043   if (observerService && gUploadLastDir) {
1044     observerService->AddObserver(gUploadLastDir, "browser:purge-session-history", true);
1045   }
1046 }
1047 
1048 void
DestroyUploadLastDir()1049 HTMLInputElement::DestroyUploadLastDir() {
1050   NS_IF_RELEASE(gUploadLastDir);
1051 }
1052 
1053 nsresult
FetchDirectoryAndDisplayPicker(nsIDocument * aDoc,nsIFilePicker * aFilePicker,nsIFilePickerShownCallback * aFpCallback)1054 UploadLastDir::FetchDirectoryAndDisplayPicker(nsIDocument* aDoc,
1055                                               nsIFilePicker* aFilePicker,
1056                                               nsIFilePickerShownCallback* aFpCallback)
1057 {
1058   NS_PRECONDITION(aDoc, "aDoc is null");
1059   NS_PRECONDITION(aFilePicker, "aFilePicker is null");
1060   NS_PRECONDITION(aFpCallback, "aFpCallback is null");
1061 
1062   nsIURI* docURI = aDoc->GetDocumentURI();
1063   NS_PRECONDITION(docURI, "docURI is null");
1064 
1065   nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
1066   nsCOMPtr<nsIContentPrefCallback2> prefCallback =
1067     new UploadLastDir::ContentPrefCallback(aFilePicker, aFpCallback);
1068 
1069 #ifdef MOZ_B2G
1070   if (XRE_IsContentProcess()) {
1071     prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
1072     return NS_OK;
1073   }
1074 #endif
1075 
1076   // Attempt to get the CPS, if it's not present we'll fallback to use the Desktop folder
1077   nsCOMPtr<nsIContentPrefService2> contentPrefService =
1078     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
1079   if (!contentPrefService) {
1080     prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
1081     return NS_OK;
1082   }
1083 
1084   nsAutoCString cstrSpec;
1085   docURI->GetSpec(cstrSpec);
1086   NS_ConvertUTF8toUTF16 spec(cstrSpec);
1087 
1088   contentPrefService->GetByDomainAndName(spec, CPS_PREF_NAME, loadContext, prefCallback);
1089   return NS_OK;
1090 }
1091 
1092 nsresult
StoreLastUsedDirectory(nsIDocument * aDoc,nsIFile * aDir)1093 UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aDir)
1094 {
1095   NS_PRECONDITION(aDoc, "aDoc is null");
1096   if (!aDir) {
1097     return NS_OK;
1098   }
1099 
1100 #ifdef MOZ_B2G
1101   if (XRE_IsContentProcess()) {
1102     return NS_OK;
1103   }
1104 #endif
1105 
1106   nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
1107   NS_PRECONDITION(docURI, "docURI is null");
1108 
1109   // Attempt to get the CPS, if it's not present we'll just return
1110   nsCOMPtr<nsIContentPrefService2> contentPrefService =
1111     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
1112   if (!contentPrefService)
1113     return NS_ERROR_NOT_AVAILABLE;
1114 
1115   nsAutoCString cstrSpec;
1116   docURI->GetSpec(cstrSpec);
1117   NS_ConvertUTF8toUTF16 spec(cstrSpec);
1118 
1119   // Find the parent of aFile, and store it
1120   nsString unicodePath;
1121   aDir->GetPath(unicodePath);
1122   if (unicodePath.IsEmpty()) // nothing to do
1123     return NS_OK;
1124   RefPtr<nsVariantCC> prefValue = new nsVariantCC();
1125   prefValue->SetAsAString(unicodePath);
1126 
1127   // Use the document's current load context to ensure that the content pref
1128   // service doesn't persistently store this directory for this domain if the
1129   // user is using private browsing:
1130   nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
1131   return contentPrefService->Set(spec, CPS_PREF_NAME, prefValue, loadContext, nullptr);
1132 }
1133 
1134 NS_IMETHODIMP
Observe(nsISupports * aSubject,char const * aTopic,char16_t const * aData)1135 UploadLastDir::Observe(nsISupports* aSubject, char const* aTopic, char16_t const* aData)
1136 {
1137   if (strcmp(aTopic, "browser:purge-session-history") == 0) {
1138     nsCOMPtr<nsIContentPrefService2> contentPrefService =
1139       do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
1140     if (contentPrefService)
1141       contentPrefService->RemoveByName(CPS_PREF_NAME, nullptr, nullptr);
1142   }
1143   return NS_OK;
1144 }
1145 
1146 #ifdef ACCESSIBILITY
1147 //Helper method
1148 static nsresult FireEventForAccessibility(nsIDOMHTMLInputElement* aTarget,
1149                                           nsPresContext* aPresContext,
1150                                           const nsAString& aEventType);
1151 #endif
1152 
1153 //
1154 // construction, destruction
1155 //
1156 
HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo> & aNodeInfo,FromParser aFromParser,FromClone aFromClone)1157 HTMLInputElement::HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
1158                                    FromParser aFromParser, FromClone aFromClone)
1159   : nsGenericHTMLFormElementWithState(aNodeInfo)
1160   , mType(kInputDefaultType->value)
1161   , mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown)
1162   , mDisabledChanged(false)
1163   , mValueChanged(false)
1164   , mLastValueChangeWasInteractive(false)
1165   , mCheckedChanged(false)
1166   , mChecked(false)
1167   , mHandlingSelectEvent(false)
1168   , mShouldInitChecked(false)
1169   , mDoneCreating(aFromParser == NOT_FROM_PARSER &&
1170                   aFromClone == FromClone::no)
1171   , mInInternalActivate(false)
1172   , mCheckedIsToggled(false)
1173   , mIndeterminate(false)
1174   , mInhibitRestoration(aFromParser & FROM_PARSER_FRAGMENT)
1175   , mCanShowValidUI(true)
1176   , mCanShowInvalidUI(true)
1177   , mHasRange(false)
1178   , mIsDraggingRange(false)
1179   , mNumberControlSpinnerIsSpinning(false)
1180   , mNumberControlSpinnerSpinsUp(false)
1181   , mPickerRunning(false)
1182   , mSelectionCached(true)
1183 {
1184   // We are in a type=text so we now we currenty need a nsTextEditorState.
1185   mInputData.mState = new nsTextEditorState(this);
1186 
1187   if (!gUploadLastDir)
1188     HTMLInputElement::InitUploadLastDir();
1189 
1190   // Set up our default state.  By default we're enabled (since we're
1191   // a control type that can be disabled but not actually disabled
1192   // right now), optional, and valid.  We are NOT readwrite by default
1193   // until someone calls UpdateEditableState on us, apparently!  Also
1194   // by default we don't have to show validity UI and so forth.
1195   AddStatesSilently(NS_EVENT_STATE_ENABLED |
1196                     NS_EVENT_STATE_OPTIONAL |
1197                     NS_EVENT_STATE_VALID);
1198   UpdateApzAwareFlag();
1199 }
1200 
~HTMLInputElement()1201 HTMLInputElement::~HTMLInputElement()
1202 {
1203   if (mNumberControlSpinnerIsSpinning) {
1204     StopNumberControlSpinnerSpin(eDisallowDispatchingEvents);
1205   }
1206   DestroyImageLoadingContent();
1207   FreeData();
1208 }
1209 
1210 void
FreeData()1211 HTMLInputElement::FreeData()
1212 {
1213   if (!IsSingleLineTextControl(false)) {
1214     free(mInputData.mValue);
1215     mInputData.mValue = nullptr;
1216   } else {
1217     UnbindFromFrame(nullptr);
1218     delete mInputData.mState;
1219     mInputData.mState = nullptr;
1220   }
1221 }
1222 
1223 nsTextEditorState*
GetEditorState() const1224 HTMLInputElement::GetEditorState() const
1225 {
1226   if (!IsSingleLineTextControl(false)) {
1227     return nullptr;
1228   }
1229 
1230   MOZ_ASSERT(mInputData.mState, "Single line text controls need to have a state"
1231                                 " associated with them");
1232 
1233   return mInputData.mState;
1234 }
1235 
1236 
1237 // nsISupports
1238 
1239 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement)
1240 
1241 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
1242                                                   nsGenericHTMLFormElementWithState)
1243   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
1244   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
1245   if (tmp->IsSingleLineTextControl(false)) {
1246     tmp->mInputData.mState->Traverse(cb);
1247   }
1248   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
1249 
1250   if (tmp->mGetFilesRecursiveHelper) {
1251     tmp->mGetFilesRecursiveHelper->Traverse(cb);
1252   }
1253 
1254   if (tmp->mGetFilesNonRecursiveHelper) {
1255     tmp->mGetFilesNonRecursiveHelper->Traverse(cb);
1256   }
1257 
1258   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
1259   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries)
1260 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1261 
1262 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
1263                                                 nsGenericHTMLFormElementWithState)
1264   NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
1265   NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
1266   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
1267   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
1268   NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries)
1269   if (tmp->IsSingleLineTextControl(false)) {
1270     tmp->mInputData.mState->Unlink();
1271   }
1272 
1273   tmp->ClearGetFilesHelpers();
1274 
1275   //XXX should unlink more?
1276 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1277 
NS_IMPL_ADDREF_INHERITED(HTMLInputElement,Element)1278 NS_IMPL_ADDREF_INHERITED(HTMLInputElement, Element)
1279 NS_IMPL_RELEASE_INHERITED(HTMLInputElement, Element)
1280 
1281 // QueryInterface implementation for HTMLInputElement
1282 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLInputElement)
1283   NS_INTERFACE_TABLE_INHERITED(HTMLInputElement,
1284                                nsIDOMHTMLInputElement,
1285                                nsITextControlElement,
1286                                nsIPhonetic,
1287                                imgINotificationObserver,
1288                                nsIImageLoadingContent,
1289                                imgIOnloadBlocker,
1290                                nsIDOMNSEditableElement,
1291                                nsIConstraintValidation)
1292 NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState)
1293 
1294 // nsIConstraintValidation
1295 NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLInputElement)
1296 
1297 // nsIDOMNode
1298 
1299 nsresult
1300 HTMLInputElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const
1301 {
1302   *aResult = nullptr;
1303 
1304   already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
1305   RefPtr<HTMLInputElement> it = new HTMLInputElement(ni, NOT_FROM_PARSER,
1306                                                      FromClone::yes);
1307 
1308   nsresult rv = const_cast<HTMLInputElement*>(this)->CopyInnerTo(it);
1309   NS_ENSURE_SUCCESS(rv, rv);
1310 
1311   switch (GetValueMode()) {
1312     case VALUE_MODE_VALUE:
1313       if (mValueChanged) {
1314         // We don't have our default value anymore.  Set our value on
1315         // the clone.
1316         nsAutoString value;
1317         GetValueInternal(value);
1318         // SetValueInternal handles setting the VALUE_CHANGED bit for us
1319         rv = it->SetValueInternal(value, nsTextEditorState::eSetValue_Notify);
1320         NS_ENSURE_SUCCESS(rv, rv);
1321       }
1322       break;
1323     case VALUE_MODE_FILENAME:
1324       if (it->OwnerDoc()->IsStaticDocument()) {
1325         // We're going to be used in print preview.  Since the doc is static
1326         // we can just grab the pretty string and use it as wallpaper
1327         GetDisplayFileName(it->mStaticDocFileList);
1328       } else {
1329         it->ClearGetFilesHelpers();
1330         it->mFilesOrDirectories.Clear();
1331         it->mFilesOrDirectories.AppendElements(mFilesOrDirectories);
1332       }
1333       break;
1334     case VALUE_MODE_DEFAULT_ON:
1335       if (mCheckedChanged) {
1336         // We no longer have our original checked state.  Set our
1337         // checked state on the clone.
1338         it->DoSetChecked(mChecked, false, true);
1339         // Then tell DoneCreatingElement() not to overwrite:
1340         it->mShouldInitChecked = false;
1341       }
1342       break;
1343     case VALUE_MODE_DEFAULT:
1344       if (mType == NS_FORM_INPUT_IMAGE && it->OwnerDoc()->IsStaticDocument()) {
1345         CreateStaticImageClone(it);
1346       }
1347       break;
1348   }
1349 
1350   it->DoneCreatingElement();
1351 
1352   it->mLastValueChangeWasInteractive = mLastValueChangeWasInteractive;
1353   it.forget(aResult);
1354   return NS_OK;
1355 }
1356 
1357 nsresult
BeforeSetAttr(int32_t aNameSpaceID,nsIAtom * aName,nsAttrValueOrString * aValue,bool aNotify)1358 HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
1359                                 nsAttrValueOrString* aValue,
1360                                 bool aNotify)
1361 {
1362   if (aNameSpaceID == kNameSpaceID_None) {
1363     //
1364     // When name or type changes, radio should be removed from radio group.
1365     // (type changes are handled in the form itself currently)
1366     // If we are not done creating the radio, we also should not do it.
1367     //
1368     if ((aName == nsGkAtoms::name ||
1369          (aName == nsGkAtoms::type && !mForm)) &&
1370         mType == NS_FORM_INPUT_RADIO &&
1371         (mForm || mDoneCreating)) {
1372       WillRemoveFromRadioGroup();
1373     } else if (aNotify && aName == nsGkAtoms::src &&
1374                mType == NS_FORM_INPUT_IMAGE) {
1375       if (aValue) {
1376         LoadImage(aValue->String(), true, aNotify, eImageLoadType_Normal);
1377       } else {
1378         // Null value means the attr got unset; drop the image
1379         CancelImageRequests(aNotify);
1380       }
1381     } else if (aNotify && aName == nsGkAtoms::disabled) {
1382       mDisabledChanged = true;
1383     } else if (aName == nsGkAtoms::dir &&
1384                AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
1385                            nsGkAtoms::_auto, eIgnoreCase)) {
1386       SetDirectionIfAuto(false, aNotify);
1387     } else if (mType == NS_FORM_INPUT_RADIO && aName == nsGkAtoms::required) {
1388       nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
1389 
1390       if (container &&
1391           ((aValue && !HasAttr(aNameSpaceID, aName)) ||
1392            (!aValue && HasAttr(aNameSpaceID, aName)))) {
1393         nsAutoString name;
1394         GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
1395         container->RadioRequiredWillChange(name, !!aValue);
1396       }
1397     }
1398 
1399     if (aName == nsGkAtoms::webkitdirectory) {
1400       Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED, true);
1401     }
1402   }
1403 
1404   return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
1405                                                           aValue, aNotify);
1406 }
1407 
1408 nsresult
AfterSetAttr(int32_t aNameSpaceID,nsIAtom * aName,const nsAttrValue * aValue,bool aNotify)1409 HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
1410                                const nsAttrValue* aValue, bool aNotify)
1411 {
1412   if (aNameSpaceID == kNameSpaceID_None) {
1413     //
1414     // When name or type changes, radio should be added to radio group.
1415     // (type changes are handled in the form itself currently)
1416     // If we are not done creating the radio, we also should not do it.
1417     //
1418     if ((aName == nsGkAtoms::name ||
1419          (aName == nsGkAtoms::type && !mForm)) &&
1420         mType == NS_FORM_INPUT_RADIO &&
1421         (mForm || mDoneCreating)) {
1422       AddedToRadioGroup();
1423       UpdateValueMissingValidityStateForRadio(false);
1424     }
1425 
1426     // If @value is changed and BF_VALUE_CHANGED is false, @value is the value
1427     // of the element so, if the value of the element is different than @value,
1428     // we have to re-set it. This is only the case when GetValueMode() returns
1429     // VALUE_MODE_VALUE.
1430     if (aName == nsGkAtoms::value &&
1431         !mValueChanged && GetValueMode() == VALUE_MODE_VALUE) {
1432       SetDefaultValueAsValue();
1433     }
1434 
1435     //
1436     // Checked must be set no matter what type of control it is, since
1437     // mChecked must reflect the new value
1438     if (aName == nsGkAtoms::checked && !mCheckedChanged) {
1439       // Delay setting checked if we are creating this element (wait
1440       // until everything is set)
1441       if (!mDoneCreating) {
1442         mShouldInitChecked = true;
1443       } else {
1444         DoSetChecked(DefaultChecked(), true, true);
1445         SetCheckedChanged(false);
1446       }
1447     }
1448 
1449     if (aName == nsGkAtoms::type) {
1450       if (!aValue) {
1451         // We're now a text input.  Note that we have to handle this manually,
1452         // since removing an attribute (which is what happened, since aValue is
1453         // null) doesn't call ParseAttribute.
1454         HandleTypeChange(kInputDefaultType->value);
1455       }
1456 
1457       UpdateBarredFromConstraintValidation();
1458 
1459       if (mType != NS_FORM_INPUT_IMAGE) {
1460         // We're no longer an image input.  Cancel our image requests, if we have
1461         // any.  Note that doing this when we already weren't an image is ok --
1462         // just does nothing.
1463         CancelImageRequests(aNotify);
1464       } else if (aNotify) {
1465         // We just got switched to be an image input; we should see
1466         // whether we have an image to load;
1467         nsAutoString src;
1468         if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
1469           LoadImage(src, false, aNotify, eImageLoadType_Normal);
1470         }
1471       }
1472 
1473       if (mType == NS_FORM_INPUT_PASSWORD && IsInComposedDoc()) {
1474         AsyncEventDispatcher* dispatcher =
1475           new AsyncEventDispatcher(this,
1476                                    NS_LITERAL_STRING("DOMInputPasswordAdded"),
1477                                    true,
1478                                    true);
1479         dispatcher->PostDOMEvent();
1480       }
1481     }
1482 
1483     if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
1484         aName == nsGkAtoms::readonly) {
1485       UpdateValueMissingValidityState();
1486 
1487       // This *has* to be called *after* validity has changed.
1488       if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
1489         UpdateBarredFromConstraintValidation();
1490       }
1491     } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::maxlength) {
1492       UpdateTooLongValidityState();
1493     } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::minlength) {
1494       UpdateTooShortValidityState();
1495     } else if (aName == nsGkAtoms::pattern && mDoneCreating) {
1496       UpdatePatternMismatchValidityState();
1497     } else if (aName == nsGkAtoms::multiple) {
1498       UpdateTypeMismatchValidityState();
1499     } else if (aName == nsGkAtoms::max) {
1500       UpdateHasRange();
1501       if (mType == NS_FORM_INPUT_RANGE) {
1502         // The value may need to change when @max changes since the value may
1503         // have been invalid and can now change to a valid value, or vice
1504         // versa. For example, consider:
1505         // <input type=range value=-1 max=1 step=3>. The valid range is 0 to 1
1506         // while the nearest valid steps are -1 and 2 (the max value having
1507         // prevented there being a valid step in range). Changing @max to/from
1508         // 1 and a number greater than on equal to 3 should change whether we
1509         // have a step mismatch or not.
1510         // The value may also need to change between a value that results in
1511         // a step mismatch and a value that results in overflow. For example,
1512         // if @max in the example above were to change from 1 to -1.
1513         nsAutoString value;
1514         GetValue(value);
1515         nsresult rv =
1516           SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
1517         NS_ENSURE_SUCCESS(rv, rv);
1518       }
1519       // Validity state must be updated *after* the SetValueInternal call above
1520       // or else the following assert will not be valid.
1521       // We don't assert the state of underflow during creation since
1522       // DoneCreatingElement sanitizes.
1523       UpdateRangeOverflowValidityState();
1524       MOZ_ASSERT(!mDoneCreating ||
1525                  mType != NS_FORM_INPUT_RANGE ||
1526                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1527                  "HTML5 spec does not allow underflow for type=range");
1528     } else if (aName == nsGkAtoms::min) {
1529       UpdateHasRange();
1530       if (mType == NS_FORM_INPUT_RANGE) {
1531         // See @max comment
1532         nsAutoString value;
1533         GetValue(value);
1534         nsresult rv =
1535           SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
1536         NS_ENSURE_SUCCESS(rv, rv);
1537       }
1538       // See corresponding @max comment
1539       UpdateRangeUnderflowValidityState();
1540       UpdateStepMismatchValidityState();
1541       MOZ_ASSERT(!mDoneCreating ||
1542                  mType != NS_FORM_INPUT_RANGE ||
1543                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1544                  "HTML5 spec does not allow underflow for type=range");
1545     } else if (aName == nsGkAtoms::step) {
1546       if (mType == NS_FORM_INPUT_RANGE) {
1547         // See @max comment
1548         nsAutoString value;
1549         GetValue(value);
1550         nsresult rv =
1551           SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
1552         NS_ENSURE_SUCCESS(rv, rv);
1553       }
1554       // See corresponding @max comment
1555       UpdateStepMismatchValidityState();
1556       MOZ_ASSERT(!mDoneCreating ||
1557                  mType != NS_FORM_INPUT_RANGE ||
1558                  !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1559                  "HTML5 spec does not allow underflow for type=range");
1560     } else if (aName == nsGkAtoms::dir &&
1561                aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
1562       SetDirectionIfAuto(true, aNotify);
1563     } else if (aName == nsGkAtoms::lang) {
1564       if (mType == NS_FORM_INPUT_NUMBER) {
1565         // Update the value that is displayed to the user to the new locale:
1566         nsAutoString value;
1567         GetValueInternal(value);
1568         nsNumberControlFrame* numberControlFrame =
1569           do_QueryFrame(GetPrimaryFrame());
1570         if (numberControlFrame) {
1571           numberControlFrame->SetValueOfAnonTextControl(value);
1572         }
1573       }
1574     } else if (aName == nsGkAtoms::autocomplete) {
1575       // Clear the cached @autocomplete attribute state.
1576       mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
1577     }
1578 
1579     UpdateState(aNotify);
1580   }
1581 
1582   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
1583                                                          aValue, aNotify);
1584 }
1585 
1586 // nsIDOMHTMLInputElement
1587 
1588 NS_IMETHODIMP
GetForm(nsIDOMHTMLFormElement ** aForm)1589 HTMLInputElement::GetForm(nsIDOMHTMLFormElement** aForm)
1590 {
1591   return nsGenericHTMLFormElementWithState::GetForm(aForm);
1592 }
1593 
NS_IMPL_STRING_ATTR(HTMLInputElement,DefaultValue,value)1594 NS_IMPL_STRING_ATTR(HTMLInputElement, DefaultValue, value)
1595 NS_IMPL_BOOL_ATTR(HTMLInputElement, DefaultChecked, checked)
1596 NS_IMPL_STRING_ATTR(HTMLInputElement, Accept, accept)
1597 NS_IMPL_STRING_ATTR(HTMLInputElement, Align, align)
1598 NS_IMPL_STRING_ATTR(HTMLInputElement, Alt, alt)
1599 NS_IMPL_BOOL_ATTR(HTMLInputElement, Autofocus, autofocus)
1600 //NS_IMPL_BOOL_ATTR(HTMLInputElement, Checked, checked)
1601 NS_IMPL_BOOL_ATTR(HTMLInputElement, Disabled, disabled)
1602 NS_IMPL_STRING_ATTR(HTMLInputElement, Max, max)
1603 NS_IMPL_STRING_ATTR(HTMLInputElement, Min, min)
1604 NS_IMPL_ACTION_ATTR(HTMLInputElement, FormAction, formaction)
1605 NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLInputElement, FormEnctype, formenctype,
1606                                                  "", kFormDefaultEnctype->tag)
1607 NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLInputElement, FormMethod, formmethod,
1608                                                  "", kFormDefaultMethod->tag)
1609 NS_IMPL_BOOL_ATTR(HTMLInputElement, FormNoValidate, formnovalidate)
1610 NS_IMPL_STRING_ATTR(HTMLInputElement, FormTarget, formtarget)
1611 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, InputMode, inputmode,
1612                                 kInputDefaultInputmode->tag)
1613 NS_IMPL_BOOL_ATTR(HTMLInputElement, Multiple, multiple)
1614 NS_IMPL_NON_NEGATIVE_INT_ATTR(HTMLInputElement, MaxLength, maxlength)
1615 NS_IMPL_NON_NEGATIVE_INT_ATTR(HTMLInputElement, MinLength, minlength)
1616 NS_IMPL_STRING_ATTR(HTMLInputElement, Name, name)
1617 NS_IMPL_BOOL_ATTR(HTMLInputElement, ReadOnly, readonly)
1618 NS_IMPL_BOOL_ATTR(HTMLInputElement, Required, required)
1619 NS_IMPL_URI_ATTR(HTMLInputElement, Src, src)
1620 NS_IMPL_STRING_ATTR(HTMLInputElement, Step, step)
1621 NS_IMPL_STRING_ATTR(HTMLInputElement, UseMap, usemap)
1622 //NS_IMPL_STRING_ATTR(HTMLInputElement, Value, value)
1623 NS_IMPL_UINT_ATTR_NON_ZERO_DEFAULT_VALUE(HTMLInputElement, Size, size, DEFAULT_COLS)
1624 NS_IMPL_STRING_ATTR(HTMLInputElement, Pattern, pattern)
1625 NS_IMPL_STRING_ATTR(HTMLInputElement, Placeholder, placeholder)
1626 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, Type, type,
1627                                 kInputDefaultType->tag)
1628 
1629 NS_IMETHODIMP
1630 HTMLInputElement::GetAutocomplete(nsAString& aValue)
1631 {
1632   if (!DoesAutocompleteApply()) {
1633     return NS_OK;
1634   }
1635 
1636   aValue.Truncate(0);
1637   const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1638 
1639   mAutocompleteAttrState =
1640     nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
1641                                                    mAutocompleteAttrState);
1642   return NS_OK;
1643 }
1644 
1645 NS_IMETHODIMP
SetAutocomplete(const nsAString & aValue)1646 HTMLInputElement::SetAutocomplete(const nsAString& aValue)
1647 {
1648   return SetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, nullptr, aValue, true);
1649 }
1650 
1651 void
GetAutocompleteInfo(Nullable<AutocompleteInfo> & aInfo)1652 HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo)
1653 {
1654   if (!DoesAutocompleteApply()) {
1655     aInfo.SetNull();
1656     return;
1657   }
1658 
1659   const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1660   mAutocompleteAttrState =
1661     nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aInfo.SetValue(),
1662                                                    mAutocompleteAttrState);
1663 }
1664 
1665 int32_t
TabIndexDefault()1666 HTMLInputElement::TabIndexDefault()
1667 {
1668   return 0;
1669 }
1670 
1671 uint32_t
Height()1672 HTMLInputElement::Height()
1673 {
1674   if (mType != NS_FORM_INPUT_IMAGE) {
1675     return 0;
1676   }
1677   return GetWidthHeightForImage(mCurrentRequest).height;
1678 }
1679 
1680 NS_IMETHODIMP
GetHeight(uint32_t * aHeight)1681 HTMLInputElement::GetHeight(uint32_t* aHeight)
1682 {
1683   *aHeight = Height();
1684   return NS_OK;
1685 }
1686 
1687 NS_IMETHODIMP
SetHeight(uint32_t aHeight)1688 HTMLInputElement::SetHeight(uint32_t aHeight)
1689 {
1690   ErrorResult rv;
1691   SetHeight(aHeight, rv);
1692   return rv.StealNSResult();
1693 }
1694 
1695 NS_IMETHODIMP
GetIndeterminate(bool * aValue)1696 HTMLInputElement::GetIndeterminate(bool* aValue)
1697 {
1698   *aValue = Indeterminate();
1699   return NS_OK;
1700 }
1701 
1702 void
SetIndeterminateInternal(bool aValue,bool aShouldInvalidate)1703 HTMLInputElement::SetIndeterminateInternal(bool aValue,
1704                                            bool aShouldInvalidate)
1705 {
1706   mIndeterminate = aValue;
1707 
1708   if (aShouldInvalidate) {
1709     // Repaint the frame
1710     nsIFrame* frame = GetPrimaryFrame();
1711     if (frame)
1712       frame->InvalidateFrameSubtree();
1713   }
1714 
1715   UpdateState(true);
1716 }
1717 
1718 NS_IMETHODIMP
SetIndeterminate(bool aValue)1719 HTMLInputElement::SetIndeterminate(bool aValue)
1720 {
1721   SetIndeterminateInternal(aValue, true);
1722   return NS_OK;
1723 }
1724 
1725 uint32_t
Width()1726 HTMLInputElement::Width()
1727 {
1728   if (mType != NS_FORM_INPUT_IMAGE) {
1729     return 0;
1730   }
1731   return GetWidthHeightForImage(mCurrentRequest).width;
1732 }
1733 
1734 NS_IMETHODIMP
GetWidth(uint32_t * aWidth)1735 HTMLInputElement::GetWidth(uint32_t* aWidth)
1736 {
1737   *aWidth = Width();
1738   return NS_OK;
1739 }
1740 
1741 NS_IMETHODIMP
SetWidth(uint32_t aWidth)1742 HTMLInputElement::SetWidth(uint32_t aWidth)
1743 {
1744   ErrorResult rv;
1745   SetWidth(aWidth, rv);
1746   return rv.StealNSResult();
1747 }
1748 
1749 NS_IMETHODIMP
GetValue(nsAString & aValue)1750 HTMLInputElement::GetValue(nsAString& aValue)
1751 {
1752   nsresult rv = GetValueInternal(aValue);
1753   if (NS_FAILED(rv)) {
1754     return rv;
1755   }
1756 
1757   // Don't return non-sanitized value for types that are experimental on mobile
1758   // or datetime types
1759   if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
1760     SanitizeValue(aValue);
1761   }
1762 
1763   return NS_OK;
1764 }
1765 
1766 nsresult
GetValueInternal(nsAString & aValue) const1767 HTMLInputElement::GetValueInternal(nsAString& aValue) const
1768 {
1769   switch (GetValueMode()) {
1770     case VALUE_MODE_VALUE:
1771       if (IsSingleLineTextControl(false)) {
1772         mInputData.mState->GetValue(aValue, true);
1773       } else if (!aValue.Assign(mInputData.mValue, fallible)) {
1774         return NS_ERROR_OUT_OF_MEMORY;
1775       }
1776       return NS_OK;
1777 
1778     case VALUE_MODE_FILENAME:
1779       if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1780         aValue.Assign(mFirstFilePath);
1781       } else {
1782         // Just return the leaf name
1783         if (mFilesOrDirectories.IsEmpty()) {
1784           aValue.Truncate();
1785         } else {
1786           GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue);
1787         }
1788       }
1789 
1790       return NS_OK;
1791 
1792     case VALUE_MODE_DEFAULT:
1793       // Treat defaultValue as value.
1794       GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue);
1795       return NS_OK;
1796 
1797     case VALUE_MODE_DEFAULT_ON:
1798       // Treat default value as value and returns "on" if no value.
1799       if (!GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue)) {
1800         aValue.AssignLiteral("on");
1801       }
1802       return NS_OK;
1803   }
1804 
1805   // This return statement is required for some compilers.
1806   return NS_OK;
1807 }
1808 
1809 bool
IsValueEmpty() const1810 HTMLInputElement::IsValueEmpty() const
1811 {
1812   nsAutoString value;
1813   GetValueInternal(value);
1814 
1815   return value.IsEmpty();
1816 }
1817 
1818 void
ClearFiles(bool aSetValueChanged)1819 HTMLInputElement::ClearFiles(bool aSetValueChanged)
1820 {
1821   nsTArray<OwningFileOrDirectory> data;
1822   SetFilesOrDirectories(data, aSetValueChanged);
1823 }
1824 
1825 int32_t
MonthsSinceJan1970(uint32_t aYear,uint32_t aMonth) const1826 HTMLInputElement::MonthsSinceJan1970(uint32_t aYear, uint32_t aMonth) const
1827 {
1828   return (aYear - 1970) * 12 + aMonth - 1;
1829 }
1830 
1831 /* static */ Decimal
StringToDecimal(const nsAString & aValue)1832 HTMLInputElement::StringToDecimal(const nsAString& aValue)
1833 {
1834   if (!IsASCII(aValue)) {
1835     return Decimal::nan();
1836   }
1837   NS_LossyConvertUTF16toASCII asciiString(aValue);
1838   std::string stdString = asciiString.get();
1839   return Decimal::fromString(stdString);
1840 }
1841 
1842 bool
ConvertStringToNumber(nsAString & aValue,Decimal & aResultValue) const1843 HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
1844                                         Decimal& aResultValue) const
1845 {
1846   MOZ_ASSERT(DoesValueAsNumberApply(),
1847              "ConvertStringToNumber only applies if .valueAsNumber applies");
1848 
1849   switch (mType) {
1850     case NS_FORM_INPUT_NUMBER:
1851     case NS_FORM_INPUT_RANGE:
1852       {
1853         aResultValue = StringToDecimal(aValue);
1854         if (!aResultValue.isFinite()) {
1855           return false;
1856         }
1857         return true;
1858       }
1859     case NS_FORM_INPUT_DATE:
1860       {
1861         uint32_t year, month, day;
1862         if (!ParseDate(aValue, &year, &month, &day)) {
1863           return false;
1864         }
1865 
1866         JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
1867         if (!time.isValid()) {
1868           return false;
1869         }
1870 
1871         aResultValue = Decimal::fromDouble(time.toDouble());
1872         return true;
1873       }
1874     case NS_FORM_INPUT_TIME:
1875       uint32_t milliseconds;
1876       if (!ParseTime(aValue, &milliseconds)) {
1877         return false;
1878       }
1879 
1880       aResultValue = Decimal(int32_t(milliseconds));
1881       return true;
1882     case NS_FORM_INPUT_MONTH:
1883       {
1884         uint32_t year, month;
1885         if (!ParseMonth(aValue, &year, &month)) {
1886           return false;
1887         }
1888 
1889         if (year < kMinimumYear || year > kMaximumYear) {
1890           return false;
1891         }
1892 
1893         // Maximum valid month is 275760-09.
1894         if (year == kMaximumYear && month > kMaximumMonthInMaximumYear) {
1895           return false;
1896         }
1897 
1898         int32_t months = MonthsSinceJan1970(year, month);
1899         aResultValue = Decimal(int32_t(months));
1900         return true;
1901       }
1902     case NS_FORM_INPUT_WEEK:
1903       {
1904         uint32_t year, week;
1905         if (!ParseWeek(aValue, &year, &week)) {
1906           return false;
1907         }
1908 
1909         if (year < kMinimumYear || year > kMaximumYear) {
1910           return false;
1911         }
1912 
1913         // Maximum week is 275760-W37, the week of 275760-09-13.
1914         if (year == kMaximumYear && week > kMaximumWeekInMaximumYear) {
1915           return false;
1916         }
1917 
1918         double days = DaysSinceEpochFromWeek(year, week);
1919         aResultValue = Decimal::fromDouble(days * kMsPerDay);
1920         return true;
1921       }
1922     default:
1923       MOZ_ASSERT(false, "Unrecognized input type");
1924       return false;
1925   }
1926 }
1927 
1928 Decimal
GetValueAsDecimal() const1929 HTMLInputElement::GetValueAsDecimal() const
1930 {
1931   Decimal decimalValue;
1932   nsAutoString stringValue;
1933 
1934   GetValueInternal(stringValue);
1935 
1936   return !ConvertStringToNumber(stringValue, decimalValue) ? Decimal::nan()
1937                                                            : decimalValue;
1938 }
1939 
1940 void
GetValue(nsAString & aValue,ErrorResult & aRv)1941 HTMLInputElement::GetValue(nsAString& aValue, ErrorResult& aRv)
1942 {
1943   nsresult rv = GetValue(aValue);
1944   if (NS_FAILED(rv)) {
1945     aRv.Throw(rv);
1946   }
1947 }
1948 
1949 void
SetValue(const nsAString & aValue,ErrorResult & aRv)1950 HTMLInputElement::SetValue(const nsAString& aValue, ErrorResult& aRv)
1951 {
1952   // check security.  Note that setting the value to the empty string is always
1953   // OK and gives pages a way to clear a file input if necessary.
1954   if (mType == NS_FORM_INPUT_FILE) {
1955     if (!aValue.IsEmpty()) {
1956       if (!nsContentUtils::IsCallerChrome()) {
1957         // setting the value of a "FILE" input widget requires
1958         // chrome privilege
1959         aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1960         return;
1961       }
1962       Sequence<nsString> list;
1963       if (!list.AppendElement(aValue, fallible)) {
1964         aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1965         return;
1966       }
1967 
1968       MozSetFileNameArray(list, aRv);
1969       return;
1970     }
1971     else {
1972       ClearFiles(true);
1973     }
1974   }
1975   else {
1976     if (MayFireChangeOnBlur()) {
1977       // If the value has been set by a script, we basically want to keep the
1978       // current change event state. If the element is ready to fire a change
1979       // event, we should keep it that way. Otherwise, we should make sure the
1980       // element will not fire any event because of the script interaction.
1981       //
1982       // NOTE: this is currently quite expensive work (too much string
1983       // manipulation). We should probably optimize that.
1984       nsAutoString currentValue;
1985       GetValue(currentValue);
1986 
1987       nsresult rv =
1988         SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent |
1989                                  nsTextEditorState::eSetValue_Notify);
1990       if (NS_FAILED(rv)) {
1991         aRv.Throw(rv);
1992         return;
1993       }
1994 
1995       if (mFocusedValue.Equals(currentValue)) {
1996         GetValue(mFocusedValue);
1997       }
1998     } else {
1999       nsresult rv =
2000         SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent |
2001                                  nsTextEditorState::eSetValue_Notify);
2002       if (NS_FAILED(rv)) {
2003         aRv.Throw(rv);
2004         return;
2005       }
2006     }
2007   }
2008 }
2009 
2010 NS_IMETHODIMP
SetValue(const nsAString & aValue)2011 HTMLInputElement::SetValue(const nsAString& aValue)
2012 {
2013   ErrorResult rv;
2014   SetValue(aValue, rv);
2015   return rv.StealNSResult();
2016 }
2017 
2018 nsGenericHTMLElement*
GetList() const2019 HTMLInputElement::GetList() const
2020 {
2021   nsAutoString dataListId;
2022   GetAttr(kNameSpaceID_None, nsGkAtoms::list, dataListId);
2023   if (dataListId.IsEmpty()) {
2024     return nullptr;
2025   }
2026 
2027   //XXXsmaug How should this all work in case input element is in Shadow DOM.
2028   nsIDocument* doc = GetUncomposedDoc();
2029   if (!doc) {
2030     return nullptr;
2031   }
2032 
2033   Element* element = doc->GetElementById(dataListId);
2034   if (!element || !element->IsHTMLElement(nsGkAtoms::datalist)) {
2035     return nullptr;
2036   }
2037 
2038   return static_cast<nsGenericHTMLElement*>(element);
2039 }
2040 
2041 NS_IMETHODIMP
GetList(nsIDOMHTMLElement ** aValue)2042 HTMLInputElement::GetList(nsIDOMHTMLElement** aValue)
2043 {
2044   *aValue = nullptr;
2045 
2046   RefPtr<nsGenericHTMLElement> element = GetList();
2047   if (!element) {
2048     return NS_OK;
2049   }
2050 
2051   element.forget(aValue);
2052   return NS_OK;
2053 }
2054 
2055 void
SetValue(Decimal aValue)2056 HTMLInputElement::SetValue(Decimal aValue)
2057 {
2058   MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!");
2059 
2060   if (aValue.isNaN()) {
2061     SetValue(EmptyString());
2062     return;
2063   }
2064 
2065   nsAutoString value;
2066   ConvertNumberToString(aValue, value);
2067   SetValue(value);
2068 }
2069 
2070 bool
ConvertNumberToString(Decimal aValue,nsAString & aResultString) const2071 HTMLInputElement::ConvertNumberToString(Decimal aValue,
2072                                         nsAString& aResultString) const
2073 {
2074   MOZ_ASSERT(DoesValueAsNumberApply(),
2075              "ConvertNumberToString is only implemented for types implementing .valueAsNumber");
2076   MOZ_ASSERT(aValue.isFinite(),
2077              "aValue must be a valid non-Infinite number.");
2078 
2079   aResultString.Truncate();
2080 
2081   switch (mType) {
2082     case NS_FORM_INPUT_NUMBER:
2083     case NS_FORM_INPUT_RANGE:
2084       {
2085         char buf[32];
2086         bool ok = aValue.toString(buf, ArrayLength(buf));
2087         aResultString.AssignASCII(buf);
2088         MOZ_ASSERT(ok, "buf not big enough");
2089         return ok;
2090       }
2091     case NS_FORM_INPUT_DATE:
2092       {
2093         // The specs (and our JS APIs) require |aValue| to be truncated.
2094         aValue = aValue.floor();
2095 
2096         double year = JS::YearFromTime(aValue.toDouble());
2097         double month = JS::MonthFromTime(aValue.toDouble());
2098         double day = JS::DayFromTime(aValue.toDouble());
2099 
2100         if (IsNaN(year) || IsNaN(month) || IsNaN(day)) {
2101           return false;
2102         }
2103 
2104         aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year,
2105                                    month + 1, day);
2106 
2107         return true;
2108       }
2109     case NS_FORM_INPUT_TIME:
2110       {
2111         // Per spec, we need to truncate |aValue| and we should only represent
2112         // times inside a day [00:00, 24:00[, which means that we should do a
2113         // modulo on |aValue| using the number of milliseconds in a day (86400000).
2114         uint32_t value = NS_floorModulo(aValue.floor(), Decimal(86400000)).toDouble();
2115 
2116         uint16_t milliseconds = value % 1000;
2117         value /= 1000;
2118 
2119         uint8_t seconds = value % 60;
2120         value /= 60;
2121 
2122         uint8_t minutes = value % 60;
2123         value /= 60;
2124 
2125         uint8_t hours = value;
2126 
2127         if (milliseconds != 0) {
2128           aResultString.AppendPrintf("%02d:%02d:%02d.%03d",
2129                                      hours, minutes, seconds, milliseconds);
2130         } else if (seconds != 0) {
2131           aResultString.AppendPrintf("%02d:%02d:%02d",
2132                                      hours, minutes, seconds);
2133         } else {
2134           aResultString.AppendPrintf("%02d:%02d", hours, minutes);
2135         }
2136 
2137         return true;
2138       }
2139     case NS_FORM_INPUT_MONTH:
2140       {
2141         aValue = aValue.floor();
2142 
2143         double month = NS_floorModulo(aValue, Decimal(12)).toDouble();
2144         month = (month < 0 ? month + 12 : month);
2145 
2146         double year = 1970 + (aValue.toDouble() - month) / 12;
2147 
2148         // Maximum valid month is 275760-09.
2149         if (year < kMinimumYear || year > kMaximumYear) {
2150           return false;
2151         }
2152 
2153         if (year == kMaximumYear && month > 8) {
2154           return false;
2155         }
2156 
2157         aResultString.AppendPrintf("%04.0f-%02.0f", year, month + 1);
2158         return true;
2159       }
2160     case NS_FORM_INPUT_WEEK:
2161       {
2162         aValue = aValue.floor();
2163 
2164         // Based on ISO 8601 date.
2165         double year = JS::YearFromTime(aValue.toDouble());
2166         double month = JS::MonthFromTime(aValue.toDouble());
2167         double day = JS::DayFromTime(aValue.toDouble());
2168         // Adding 1 since day starts from 0.
2169         double dayInYear = JS::DayWithinYear(aValue.toDouble(), year) + 1;
2170 
2171         // Adding 1 since month starts from 0.
2172         uint32_t isoWeekday = DayOfWeek(year, month + 1, day, true);
2173         // Target on Wednesday since ISO 8601 states that week 1 is the week
2174         // with the first Thursday of that year.
2175         uint32_t week = (dayInYear - isoWeekday + 10) / 7;
2176 
2177         if (week < 1) {
2178           year--;
2179           if (year < 1) {
2180             return false;
2181           }
2182           week = MaximumWeekInYear(year);
2183         } else if (week > MaximumWeekInYear(year)) {
2184           year++;
2185           if (year > kMaximumYear ||
2186               (year == kMaximumYear && week > kMaximumWeekInMaximumYear)) {
2187             return false;
2188           }
2189           week = 1;
2190         }
2191 
2192         aResultString.AppendPrintf("%04.0f-W%02d", year, week);
2193         return true;
2194       }
2195     default:
2196       MOZ_ASSERT(false, "Unrecognized input type");
2197       return false;
2198   }
2199 }
2200 
2201 
2202 Nullable<Date>
GetValueAsDate(ErrorResult & aRv)2203 HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
2204 {
2205   // TODO: this is temporary until bug 888331 is fixed.
2206   if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
2207     return Nullable<Date>();
2208   }
2209 
2210   switch (mType) {
2211     case NS_FORM_INPUT_DATE:
2212     {
2213       uint32_t year, month, day;
2214       nsAutoString value;
2215       GetValueInternal(value);
2216       if (!ParseDate(value, &year, &month, &day)) {
2217         return Nullable<Date>();
2218       }
2219 
2220       JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
2221       return Nullable<Date>(Date(time));
2222     }
2223     case NS_FORM_INPUT_TIME:
2224     {
2225       uint32_t millisecond;
2226       nsAutoString value;
2227       GetValueInternal(value);
2228       if (!ParseTime(value, &millisecond)) {
2229         return Nullable<Date>();
2230       }
2231 
2232       JS::ClippedTime time = JS::TimeClip(millisecond);
2233       MOZ_ASSERT(time.toDouble() == millisecond,
2234                  "HTML times are restricted to the day after the epoch and "
2235                  "never clip");
2236       return Nullable<Date>(Date(time));
2237     }
2238     case NS_FORM_INPUT_MONTH:
2239     {
2240       uint32_t year, month;
2241       nsAutoString value;
2242       GetValueInternal(value);
2243       if (!ParseMonth(value, &year, &month)) {
2244         return Nullable<Date>();
2245       }
2246 
2247       JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, 1));
2248       return Nullable<Date>(Date(time));
2249     }
2250     case NS_FORM_INPUT_WEEK:
2251     {
2252       uint32_t year, week;
2253       nsAutoString value;
2254       GetValueInternal(value);
2255       if (!ParseWeek(value, &year, &week)) {
2256         return Nullable<Date>();
2257       }
2258 
2259       double days = DaysSinceEpochFromWeek(year, week);
2260       JS::ClippedTime time = JS::TimeClip(days * kMsPerDay);
2261 
2262       return Nullable<Date>(Date(time));
2263     }
2264   }
2265 
2266   MOZ_ASSERT(false, "Unrecognized input type");
2267   aRv.Throw(NS_ERROR_UNEXPECTED);
2268   return Nullable<Date>();
2269 }
2270 
2271 void
SetValueAsDate(Nullable<Date> aDate,ErrorResult & aRv)2272 HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv)
2273 {
2274   // TODO: this is temporary until bug 888331 is fixed.
2275   if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
2276     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2277     return;
2278   }
2279 
2280   if (aDate.IsNull() || aDate.Value().IsUndefined()) {
2281     aRv = SetValue(EmptyString());
2282     return;
2283   }
2284 
2285   double milliseconds = aDate.Value().TimeStamp().toDouble();
2286 
2287   if (mType != NS_FORM_INPUT_MONTH) {
2288     SetValue(Decimal::fromDouble(milliseconds));
2289     return;
2290   }
2291 
2292   // type=month expects the value to be number of months.
2293   double year = JS::YearFromTime(milliseconds);
2294   double month = JS::MonthFromTime(milliseconds);
2295 
2296   if (IsNaN(year) || IsNaN(month)) {
2297     SetValue(EmptyString());
2298     return;
2299   }
2300 
2301   int32_t months = MonthsSinceJan1970(year, month + 1);
2302   SetValue(Decimal(int32_t(months)));
2303 }
2304 
2305 NS_IMETHODIMP
GetValueAsNumber(double * aValueAsNumber)2306 HTMLInputElement::GetValueAsNumber(double* aValueAsNumber)
2307 {
2308   *aValueAsNumber = ValueAsNumber();
2309   return NS_OK;
2310 }
2311 
2312 void
SetValueAsNumber(double aValueAsNumber,ErrorResult & aRv)2313 HTMLInputElement::SetValueAsNumber(double aValueAsNumber, ErrorResult& aRv)
2314 {
2315   // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
2316   // bug 825197.
2317   if (IsInfinite(aValueAsNumber)) {
2318     aRv.Throw(NS_ERROR_INVALID_ARG);
2319     return;
2320   }
2321 
2322   if (!DoesValueAsNumberApply()) {
2323     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2324     return;
2325   }
2326 
2327   SetValue(Decimal::fromDouble(aValueAsNumber));
2328 }
2329 
2330 NS_IMETHODIMP
SetValueAsNumber(double aValueAsNumber)2331 HTMLInputElement::SetValueAsNumber(double aValueAsNumber)
2332 {
2333   ErrorResult rv;
2334   SetValueAsNumber(aValueAsNumber, rv);
2335   return rv.StealNSResult();
2336 }
2337 
2338 Decimal
GetMinimum() const2339 HTMLInputElement::GetMinimum() const
2340 {
2341   MOZ_ASSERT(DoesValueAsNumberApply(),
2342              "GetMinimum() should only be used for types that allow .valueAsNumber");
2343 
2344   // Only type=range has a default minimum
2345   Decimal defaultMinimum =
2346     mType == NS_FORM_INPUT_RANGE ? Decimal(0) : Decimal::nan();
2347 
2348   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
2349     return defaultMinimum;
2350   }
2351 
2352   nsAutoString minStr;
2353   GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
2354 
2355   Decimal min;
2356   return ConvertStringToNumber(minStr, min) ? min : defaultMinimum;
2357 }
2358 
2359 Decimal
GetMaximum() const2360 HTMLInputElement::GetMaximum() const
2361 {
2362   MOZ_ASSERT(DoesValueAsNumberApply(),
2363              "GetMaximum() should only be used for types that allow .valueAsNumber");
2364 
2365   // Only type=range has a default maximum
2366   Decimal defaultMaximum =
2367     mType == NS_FORM_INPUT_RANGE ? Decimal(100) : Decimal::nan();
2368 
2369   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
2370     return defaultMaximum;
2371   }
2372 
2373   nsAutoString maxStr;
2374   GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
2375 
2376   Decimal max;
2377   return ConvertStringToNumber(maxStr, max) ? max : defaultMaximum;
2378 }
2379 
2380 Decimal
GetStepBase() const2381 HTMLInputElement::GetStepBase() const
2382 {
2383   MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER ||
2384              mType == NS_FORM_INPUT_DATE ||
2385              mType == NS_FORM_INPUT_TIME ||
2386              mType == NS_FORM_INPUT_MONTH ||
2387              mType == NS_FORM_INPUT_WEEK ||
2388              mType == NS_FORM_INPUT_RANGE,
2389              "Check that kDefaultStepBase is correct for this new type");
2390 
2391   Decimal stepBase;
2392 
2393   // Do NOT use GetMinimum here - the spec says to use "the min content
2394   // attribute", not "the minimum".
2395   nsAutoString minStr;
2396   if (GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr) &&
2397       ConvertStringToNumber(minStr, stepBase)) {
2398     return stepBase;
2399   }
2400 
2401   // If @min is not a double, we should use @value.
2402   nsAutoString valueStr;
2403   if (GetAttr(kNameSpaceID_None, nsGkAtoms::value, valueStr) &&
2404       ConvertStringToNumber(valueStr, stepBase)) {
2405     return stepBase;
2406   }
2407 
2408   if (mType == NS_FORM_INPUT_WEEK) {
2409     return kDefaultStepBaseWeek;
2410   }
2411 
2412   return kDefaultStepBase;
2413 }
2414 
2415 nsresult
GetValueIfStepped(int32_t aStep,StepCallerType aCallerType,Decimal * aNextStep)2416 HTMLInputElement::GetValueIfStepped(int32_t aStep,
2417                                     StepCallerType aCallerType,
2418                                     Decimal* aNextStep)
2419 {
2420   if (!DoStepDownStepUpApply()) {
2421     return NS_ERROR_DOM_INVALID_STATE_ERR;
2422   }
2423 
2424   Decimal stepBase = GetStepBase();
2425   Decimal step = GetStep();
2426   if (step == kStepAny) {
2427     if (aCallerType != CALLED_FOR_USER_EVENT) {
2428       return NS_ERROR_DOM_INVALID_STATE_ERR;
2429     }
2430     // Allow the spin buttons and up/down arrow keys to do something sensible:
2431     step = GetDefaultStep();
2432   }
2433 
2434   Decimal minimum = GetMinimum();
2435   Decimal maximum = GetMaximum();
2436 
2437   if (!maximum.isNaN()) {
2438     // "max - (max - stepBase) % step" is the nearest valid value to max.
2439     maximum = maximum - NS_floorModulo(maximum - stepBase, step);
2440     if (!minimum.isNaN()) {
2441       if (minimum > maximum) {
2442         // Either the minimum was greater than the maximum prior to our
2443         // adjustment to align maximum on a step, or else (if we adjusted
2444         // maximum) there is no valid step between minimum and the unadjusted
2445         // maximum.
2446         return NS_OK;
2447       }
2448     }
2449   }
2450 
2451   Decimal value = GetValueAsDecimal();
2452   bool valueWasNaN = false;
2453   if (value.isNaN()) {
2454     value = Decimal(0);
2455     valueWasNaN = true;
2456   }
2457   Decimal valueBeforeStepping = value;
2458 
2459   Decimal deltaFromStep = NS_floorModulo(value - stepBase, step);
2460 
2461   if (deltaFromStep != Decimal(0)) {
2462     if (aStep > 0) {
2463       value += step - deltaFromStep;      // partial step
2464       value += step * Decimal(aStep - 1); // then remaining steps
2465     } else if (aStep < 0) {
2466       value -= deltaFromStep;             // partial step
2467       value += step * Decimal(aStep + 1); // then remaining steps
2468     }
2469   } else {
2470     value += step * Decimal(aStep);
2471   }
2472 
2473   if (value < minimum) {
2474     value = minimum;
2475     deltaFromStep = NS_floorModulo(value - stepBase, step);
2476     if (deltaFromStep != Decimal(0)) {
2477       value += step - deltaFromStep;
2478     }
2479   }
2480   if (value > maximum) {
2481     value = maximum;
2482     deltaFromStep = NS_floorModulo(value - stepBase, step);
2483     if (deltaFromStep != Decimal(0)) {
2484       value -= deltaFromStep;
2485     }
2486   }
2487 
2488   if (!valueWasNaN && // value="", resulting in us using "0"
2489       ((aStep > 0 && value < valueBeforeStepping) ||
2490        (aStep < 0 && value > valueBeforeStepping))) {
2491     // We don't want step-up to effectively step down, or step-down to
2492     // effectively step up, so return;
2493     return NS_OK;
2494   }
2495 
2496   *aNextStep = value;
2497   return NS_OK;
2498 }
2499 
2500 nsresult
ApplyStep(int32_t aStep)2501 HTMLInputElement::ApplyStep(int32_t aStep)
2502 {
2503   Decimal nextStep = Decimal::nan(); // unchanged if value will not change
2504 
2505   nsresult rv = GetValueIfStepped(aStep, CALLED_FOR_SCRIPT, &nextStep);
2506 
2507   if (NS_SUCCEEDED(rv) && nextStep.isFinite()) {
2508     SetValue(nextStep);
2509   }
2510 
2511   return rv;
2512 }
2513 
2514 /* static */
2515 bool
IsExperimentalMobileType(uint8_t aType)2516 HTMLInputElement::IsExperimentalMobileType(uint8_t aType)
2517 {
2518   return (aType == NS_FORM_INPUT_DATE &&
2519     !Preferences::GetBool("dom.forms.datetime", false) &&
2520     !Preferences::GetBool("dom.forms.datepicker", false)) ||
2521     (aType == NS_FORM_INPUT_TIME &&
2522      !Preferences::GetBool("dom.forms.datetime", false));
2523 }
2524 
2525 bool
IsDateTimeInputType(uint8_t aType)2526 HTMLInputElement::IsDateTimeInputType(uint8_t aType)
2527 {
2528   return aType == NS_FORM_INPUT_DATE ||
2529          aType == NS_FORM_INPUT_TIME ||
2530          aType == NS_FORM_INPUT_MONTH ||
2531          aType == NS_FORM_INPUT_WEEK ||
2532          aType == NS_FORM_INPUT_DATETIME_LOCAL;
2533 }
2534 
2535 NS_IMETHODIMP
StepDown(int32_t n,uint8_t optional_argc)2536 HTMLInputElement::StepDown(int32_t n, uint8_t optional_argc)
2537 {
2538   return ApplyStep(optional_argc ? -n : -1);
2539 }
2540 
2541 NS_IMETHODIMP
StepUp(int32_t n,uint8_t optional_argc)2542 HTMLInputElement::StepUp(int32_t n, uint8_t optional_argc)
2543 {
2544   return ApplyStep(optional_argc ? n : 1);
2545 }
2546 
2547 void
FlushFrames()2548 HTMLInputElement::FlushFrames()
2549 {
2550   if (GetComposedDoc()) {
2551     GetComposedDoc()->FlushPendingNotifications(Flush_Frames);
2552   }
2553 }
2554 
2555 void
MozGetFileNameArray(nsTArray<nsString> & aArray,ErrorResult & aRv)2556 HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray,
2557                                       ErrorResult& aRv)
2558 {
2559   for (uint32_t i = 0; i < mFilesOrDirectories.Length(); i++) {
2560     nsAutoString str;
2561     GetDOMFileOrDirectoryPath(mFilesOrDirectories[i], str, aRv);
2562     if (NS_WARN_IF(aRv.Failed())) {
2563       return;
2564     }
2565 
2566     aArray.AppendElement(str);
2567   }
2568 }
2569 
2570 
2571 NS_IMETHODIMP
MozGetFileNameArray(uint32_t * aLength,char16_t *** aFileNames)2572 HTMLInputElement::MozGetFileNameArray(uint32_t* aLength, char16_t*** aFileNames)
2573 {
2574   if (!nsContentUtils::IsCallerChrome()) {
2575     // Since this function returns full paths it's important that normal pages
2576     // can't call it.
2577     return NS_ERROR_DOM_SECURITY_ERR;
2578   }
2579 
2580   ErrorResult rv;
2581   nsTArray<nsString> array;
2582   MozGetFileNameArray(array, rv);
2583   if (NS_WARN_IF(rv.Failed())) {
2584     return rv.StealNSResult();
2585   }
2586 
2587   *aLength = array.Length();
2588   char16_t** ret =
2589     static_cast<char16_t**>(moz_xmalloc(*aLength * sizeof(char16_t*)));
2590   if (!ret) {
2591     return NS_ERROR_OUT_OF_MEMORY;
2592   }
2593 
2594   for (uint32_t i = 0; i < *aLength; ++i) {
2595     ret[i] = NS_strdup(array[i].get());
2596   }
2597 
2598   *aFileNames = ret;
2599 
2600   return NS_OK;
2601 }
2602 
2603 void
MozSetFileArray(const Sequence<OwningNonNull<File>> & aFiles)2604 HTMLInputElement::MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles)
2605 {
2606   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2607   MOZ_ASSERT(global);
2608   if (!global) {
2609     return;
2610   }
2611 
2612   nsTArray<OwningFileOrDirectory> files;
2613   for (uint32_t i = 0; i < aFiles.Length(); ++i) {
2614     RefPtr<File> file = File::Create(global, aFiles[i].get()->Impl());
2615     MOZ_ASSERT(file);
2616 
2617     OwningFileOrDirectory* element = files.AppendElement();
2618     element->SetAsFile() = file;
2619   }
2620 
2621   SetFilesOrDirectories(files, true);
2622 }
2623 
2624 void
MozSetFileNameArray(const Sequence<nsString> & aFileNames,ErrorResult & aRv)2625 HTMLInputElement::MozSetFileNameArray(const Sequence<nsString>& aFileNames,
2626                                       ErrorResult& aRv)
2627 {
2628   if (XRE_IsContentProcess()) {
2629     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
2630     return;
2631   }
2632 
2633   nsTArray<OwningFileOrDirectory> files;
2634   for (uint32_t i = 0; i < aFileNames.Length(); ++i) {
2635     nsCOMPtr<nsIFile> file;
2636 
2637     if (StringBeginsWith(aFileNames[i], NS_LITERAL_STRING("file:"),
2638                          nsASCIICaseInsensitiveStringComparator())) {
2639       // Converts the URL string into the corresponding nsIFile if possible
2640       // A local file will be created if the URL string begins with file://
2641       NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames[i]),
2642                             getter_AddRefs(file));
2643     }
2644 
2645     if (!file) {
2646       // this is no "file://", try as local file
2647       NS_NewLocalFile(aFileNames[i], false, getter_AddRefs(file));
2648     }
2649 
2650     if (!file) {
2651       continue; // Not much we can do if the file doesn't exist
2652     }
2653 
2654     nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2655     if (!global) {
2656       aRv.Throw(NS_ERROR_FAILURE);
2657       return;
2658     }
2659 
2660     RefPtr<File> domFile = File::CreateFromFile(global, file);
2661 
2662     OwningFileOrDirectory* element = files.AppendElement();
2663     element->SetAsFile() = domFile;
2664   }
2665 
2666   SetFilesOrDirectories(files, true);
2667 }
2668 
2669 NS_IMETHODIMP
MozSetFileNameArray(const char16_t ** aFileNames,uint32_t aLength)2670 HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames,
2671                                       uint32_t aLength)
2672 {
2673   if (!nsContentUtils::IsCallerChrome()) {
2674     // setting the value of a "FILE" input widget requires chrome privilege
2675     return NS_ERROR_DOM_SECURITY_ERR;
2676   }
2677 
2678   Sequence<nsString> list;
2679   nsString* names = list.AppendElements(aLength, fallible);
2680   if (!names) {
2681     return NS_ERROR_OUT_OF_MEMORY;
2682   }
2683   for (uint32_t i = 0; i < aLength; ++i) {
2684     const char16_t* filename = aFileNames[i];
2685     names[i].Rebind(filename, nsCharTraits<char16_t>::length(filename));
2686   }
2687 
2688   ErrorResult rv;
2689   MozSetFileNameArray(list, rv);
2690   return rv.StealNSResult();
2691 }
2692 
2693 void
MozSetDirectory(const nsAString & aDirectoryPath,ErrorResult & aRv)2694 HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
2695                                   ErrorResult& aRv)
2696 {
2697   nsCOMPtr<nsIFile> file;
2698   aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));
2699   if (NS_WARN_IF(aRv.Failed())) {
2700     return;
2701   }
2702 
2703   nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
2704   if (NS_WARN_IF(!window)) {
2705     aRv.Throw(NS_ERROR_FAILURE);
2706     return;
2707   }
2708 
2709   RefPtr<Directory> directory = Directory::Create(window, file);
2710   MOZ_ASSERT(directory);
2711 
2712   nsTArray<OwningFileOrDirectory> array;
2713   OwningFileOrDirectory* element = array.AppendElement();
2714   element->SetAsDirectory() = directory;
2715 
2716   SetFilesOrDirectories(array, true);
2717 }
2718 
GetDateTimeInputBoxValue(DateTimeValue & aValue)2719 void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue)
2720 {
2721   if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
2722     return;
2723   }
2724 
2725   aValue = *mDateTimeInputBoxValue;
2726 }
2727 
2728 void
UpdateDateTimeInputBox(const DateTimeValue & aValue)2729 HTMLInputElement::UpdateDateTimeInputBox(const DateTimeValue& aValue)
2730 {
2731   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2732     return;
2733   }
2734 
2735   nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
2736   if (frame) {
2737     frame->SetValueFromPicker(aValue);
2738   }
2739 }
2740 
2741 void
SetDateTimePickerState(bool aOpen)2742 HTMLInputElement::SetDateTimePickerState(bool aOpen)
2743 {
2744   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2745     return;
2746   }
2747 
2748   nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
2749   if (frame) {
2750     frame->SetPickerState(aOpen);
2751   }
2752 }
2753 
2754 void
OpenDateTimePicker(const DateTimeValue & aInitialValue)2755 HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue)
2756 {
2757   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2758     return;
2759   }
2760 
2761   mDateTimeInputBoxValue = new DateTimeValue(aInitialValue);
2762   nsContentUtils::DispatchChromeEvent(OwnerDoc(),
2763                                       static_cast<nsIDOMHTMLInputElement*>(this),
2764                                       NS_LITERAL_STRING("MozOpenDateTimePicker"),
2765                                       true, true);
2766 }
2767 
2768 void
UpdateDateTimePicker(const DateTimeValue & aValue)2769 HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue)
2770 {
2771   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2772     return;
2773   }
2774 
2775   mDateTimeInputBoxValue = new DateTimeValue(aValue);
2776   nsContentUtils::DispatchChromeEvent(OwnerDoc(),
2777                                       static_cast<nsIDOMHTMLInputElement*>(this),
2778                                       NS_LITERAL_STRING("MozUpdateDateTimePicker"),
2779                                       true, true);
2780 }
2781 
2782 void
CloseDateTimePicker()2783 HTMLInputElement::CloseDateTimePicker()
2784 {
2785   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2786     return;
2787   }
2788 
2789   nsContentUtils::DispatchChromeEvent(OwnerDoc(),
2790                                       static_cast<nsIDOMHTMLInputElement*>(this),
2791                                       NS_LITERAL_STRING("MozCloseDateTimePicker"),
2792                                       true, true);
2793 }
2794 
2795 bool
MozIsTextField(bool aExcludePassword)2796 HTMLInputElement::MozIsTextField(bool aExcludePassword)
2797 {
2798   // TODO: temporary until bug 888320 is fixed.
2799   if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
2800     return false;
2801   }
2802 
2803   return IsSingleLineTextControl(aExcludePassword);
2804 }
2805 
2806 HTMLInputElement*
GetOwnerNumberControl()2807 HTMLInputElement::GetOwnerNumberControl()
2808 {
2809   if (IsInNativeAnonymousSubtree() &&
2810       mType == NS_FORM_INPUT_TEXT &&
2811       GetParent() && GetParent()->GetParent()) {
2812     HTMLInputElement* grandparent =
2813       HTMLInputElement::FromContentOrNull(GetParent()->GetParent());
2814     if (grandparent && grandparent->mType == NS_FORM_INPUT_NUMBER) {
2815       return grandparent;
2816     }
2817   }
2818   return nullptr;
2819 }
2820 
2821 HTMLInputElement*
GetOwnerDateTimeControl()2822 HTMLInputElement::GetOwnerDateTimeControl()
2823 {
2824   if (IsInNativeAnonymousSubtree() &&
2825       mType == NS_FORM_INPUT_TEXT &&
2826       GetParent() &&
2827       GetParent()->GetParent() &&
2828       GetParent()->GetParent()->GetParent() &&
2829       GetParent()->GetParent()->GetParent()->GetParent()) {
2830     // Yes, this is very very deep.
2831     HTMLInputElement* ownerDateTimeControl =
2832       HTMLInputElement::FromContentOrNull(
2833         GetParent()->GetParent()->GetParent()->GetParent());
2834     if (ownerDateTimeControl &&
2835         ownerDateTimeControl->mType == NS_FORM_INPUT_TIME) {
2836       return ownerDateTimeControl;
2837     }
2838   }
2839   return nullptr;
2840 }
2841 
2842 
2843 NS_IMETHODIMP
MozIsTextField(bool aExcludePassword,bool * aResult)2844 HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
2845 {
2846   *aResult = MozIsTextField(aExcludePassword);
2847   return NS_OK;
2848 }
2849 
2850 void
SetUserInput(const nsAString & aInput,nsIPrincipal & aSubjectPrincipal)2851 HTMLInputElement::SetUserInput(const nsAString& aInput,
2852                                nsIPrincipal& aSubjectPrincipal) {
2853   if (mType == NS_FORM_INPUT_FILE &&
2854       !nsContentUtils::IsSystemPrincipal(&aSubjectPrincipal)) {
2855     return;
2856   }
2857 
2858   SetUserInput(aInput);
2859 }
2860 
2861 NS_IMETHODIMP
SetUserInput(const nsAString & aValue)2862 HTMLInputElement::SetUserInput(const nsAString& aValue)
2863 {
2864   if (mType == NS_FORM_INPUT_FILE)
2865   {
2866     Sequence<nsString> list;
2867     if (!list.AppendElement(aValue, fallible)) {
2868       return NS_ERROR_OUT_OF_MEMORY;
2869     }
2870 
2871     ErrorResult rv;
2872     MozSetFileNameArray(list, rv);
2873     return rv.StealNSResult();
2874   } else {
2875     nsresult rv =
2876       SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput |
2877                                nsTextEditorState::eSetValue_Notify);
2878     NS_ENSURE_SUCCESS(rv, rv);
2879   }
2880 
2881   nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
2882                                        static_cast<nsIDOMHTMLInputElement*>(this),
2883                                        NS_LITERAL_STRING("input"), true,
2884                                        true);
2885 
2886   // If this element is not currently focused, it won't receive a change event for this
2887   // update through the normal channels. So fire a change event immediately, instead.
2888   if (!ShouldBlur(this)) {
2889     FireChangeEventIfNeeded();
2890   }
2891 
2892   return NS_OK;
2893 }
2894 
2895 nsIEditor*
GetEditor()2896 HTMLInputElement::GetEditor()
2897 {
2898   nsTextEditorState* state = GetEditorState();
2899   if (state) {
2900     return state->GetEditor();
2901   }
2902   return nullptr;
2903 }
2904 
NS_IMETHODIMP_(nsIEditor *)2905 NS_IMETHODIMP_(nsIEditor*)
2906 HTMLInputElement::GetTextEditor()
2907 {
2908   return GetEditor();
2909 }
2910 
NS_IMETHODIMP_(nsISelectionController *)2911 NS_IMETHODIMP_(nsISelectionController*)
2912 HTMLInputElement::GetSelectionController()
2913 {
2914   nsTextEditorState* state = GetEditorState();
2915   if (state) {
2916     return state->GetSelectionController();
2917   }
2918   return nullptr;
2919 }
2920 
2921 nsFrameSelection*
GetConstFrameSelection()2922 HTMLInputElement::GetConstFrameSelection()
2923 {
2924   nsTextEditorState* state = GetEditorState();
2925   if (state) {
2926     return state->GetConstFrameSelection();
2927   }
2928   return nullptr;
2929 }
2930 
2931 NS_IMETHODIMP
BindToFrame(nsTextControlFrame * aFrame)2932 HTMLInputElement::BindToFrame(nsTextControlFrame* aFrame)
2933 {
2934   nsTextEditorState* state = GetEditorState();
2935   if (state) {
2936     return state->BindToFrame(aFrame);
2937   }
2938   return NS_ERROR_FAILURE;
2939 }
2940 
NS_IMETHODIMP_(void)2941 NS_IMETHODIMP_(void)
2942 HTMLInputElement::UnbindFromFrame(nsTextControlFrame* aFrame)
2943 {
2944   nsTextEditorState* state = GetEditorState();
2945   if (state && aFrame) {
2946     state->UnbindFromFrame(aFrame);
2947   }
2948 }
2949 
2950 NS_IMETHODIMP
CreateEditor()2951 HTMLInputElement::CreateEditor()
2952 {
2953   nsTextEditorState* state = GetEditorState();
2954   if (state) {
2955     return state->PrepareEditor();
2956   }
2957   return NS_ERROR_FAILURE;
2958 }
2959 
NS_IMETHODIMP_(nsIContent *)2960 NS_IMETHODIMP_(nsIContent*)
2961 HTMLInputElement::GetRootEditorNode()
2962 {
2963   nsTextEditorState* state = GetEditorState();
2964   if (state) {
2965     return state->GetRootNode();
2966   }
2967   return nullptr;
2968 }
2969 
NS_IMETHODIMP_(Element *)2970 NS_IMETHODIMP_(Element*)
2971 HTMLInputElement::CreatePlaceholderNode()
2972 {
2973   nsTextEditorState* state = GetEditorState();
2974   if (state) {
2975     NS_ENSURE_SUCCESS(state->CreatePlaceholderNode(), nullptr);
2976     return state->GetPlaceholderNode();
2977   }
2978   return nullptr;
2979 }
2980 
NS_IMETHODIMP_(Element *)2981 NS_IMETHODIMP_(Element*)
2982 HTMLInputElement::GetPlaceholderNode()
2983 {
2984   nsTextEditorState* state = GetEditorState();
2985   if (state) {
2986     return state->GetPlaceholderNode();
2987   }
2988   return nullptr;
2989 }
2990 
NS_IMETHODIMP_(void)2991 NS_IMETHODIMP_(void)
2992 HTMLInputElement::UpdatePlaceholderVisibility(bool aNotify)
2993 {
2994   nsTextEditorState* state = GetEditorState();
2995   if (state) {
2996     state->UpdatePlaceholderVisibility(aNotify);
2997   }
2998 }
2999 
NS_IMETHODIMP_(bool)3000 NS_IMETHODIMP_(bool)
3001 HTMLInputElement::GetPlaceholderVisibility()
3002 {
3003   nsTextEditorState* state = GetEditorState();
3004   if (!state) {
3005     return false;
3006   }
3007 
3008   return state->GetPlaceholderVisibility();
3009 }
3010 
3011 void
GetDisplayFileName(nsAString & aValue) const3012 HTMLInputElement::GetDisplayFileName(nsAString& aValue) const
3013 {
3014   if (OwnerDoc()->IsStaticDocument()) {
3015     aValue = mStaticDocFileList;
3016     return;
3017   }
3018 
3019   if (mFilesOrDirectories.Length() == 1) {
3020     GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue);
3021     return;
3022   }
3023 
3024   nsXPIDLString value;
3025 
3026   if (mFilesOrDirectories.IsEmpty()) {
3027     if ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
3028         (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
3029          HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
3030       nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
3031                                          "NoDirSelected", value);
3032     } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
3033       nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
3034                                          "NoFilesSelected", value);
3035     } else {
3036       nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
3037                                          "NoFileSelected", value);
3038     }
3039   } else {
3040     nsString count;
3041     count.AppendInt(int(mFilesOrDirectories.Length()));
3042 
3043     const char16_t* params[] = { count.get() };
3044     nsContentUtils::FormatLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
3045                                           "XFilesSelected", params, value);
3046   }
3047 
3048   aValue = value;
3049 }
3050 
3051 void
SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories,bool aSetValueChanged)3052 HTMLInputElement::SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
3053                                         bool aSetValueChanged)
3054 {
3055   ClearGetFilesHelpers();
3056 
3057   if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
3058     HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
3059     mEntries.Clear();
3060   }
3061 
3062   mFilesOrDirectories.Clear();
3063   mFilesOrDirectories.AppendElements(aFilesOrDirectories);
3064 
3065   AfterSetFilesOrDirectories(aSetValueChanged);
3066 }
3067 
3068 void
SetFiles(nsIDOMFileList * aFiles,bool aSetValueChanged)3069 HTMLInputElement::SetFiles(nsIDOMFileList* aFiles,
3070                            bool aSetValueChanged)
3071 {
3072   RefPtr<FileList> files = static_cast<FileList*>(aFiles);
3073   mFilesOrDirectories.Clear();
3074   ClearGetFilesHelpers();
3075 
3076   if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
3077     HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
3078     mEntries.Clear();
3079   }
3080 
3081   if (aFiles) {
3082     uint32_t listLength;
3083     aFiles->GetLength(&listLength);
3084     for (uint32_t i = 0; i < listLength; i++) {
3085       OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
3086       element->SetAsFile() = files->Item(i);
3087     }
3088   }
3089 
3090   AfterSetFilesOrDirectories(aSetValueChanged);
3091 }
3092 
3093 // This method is used for testing only.
3094 void
MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories)3095 HTMLInputElement::MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
3096 {
3097   SetFilesOrDirectories(aFilesOrDirectories, true);
3098 
3099   if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
3100     UpdateEntries(aFilesOrDirectories);
3101   }
3102 
3103   RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
3104     new DispatchChangeEventCallback(this);
3105 
3106   if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
3107       HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
3108     ErrorResult rv;
3109     GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
3110                                                        rv);
3111     if (NS_WARN_IF(rv.Failed())) {
3112       rv.SuppressException();
3113       return;
3114     }
3115 
3116     helper->AddCallback(dispatchChangeEventCallback);
3117   } else {
3118     dispatchChangeEventCallback->DispatchEvents();
3119   }
3120 }
3121 
3122 void
AfterSetFilesOrDirectories(bool aSetValueChanged)3123 HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged)
3124 {
3125   // No need to flush here, if there's no frame at this point we
3126   // don't need to force creation of one just to tell it about this
3127   // new value.  We just want the display to update as needed.
3128   nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
3129   if (formControlFrame) {
3130     nsAutoString readableValue;
3131     GetDisplayFileName(readableValue);
3132     formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue);
3133   }
3134 
3135   // Grab the full path here for any chrome callers who access our .value via a
3136   // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
3137   // call under GetMozFullPath won't be rejected for not being urgent.
3138   // XXX Protected by the ifndef because the blob code doesn't allow us to send
3139   // this message in b2g.
3140   if (mFilesOrDirectories.IsEmpty()) {
3141     mFirstFilePath.Truncate();
3142   } else {
3143     ErrorResult rv;
3144     GetDOMFileOrDirectoryPath(mFilesOrDirectories[0], mFirstFilePath, rv);
3145     if (NS_WARN_IF(rv.Failed())) {
3146       rv.SuppressException();
3147     }
3148   }
3149 
3150   UpdateFileList();
3151 
3152   if (aSetValueChanged) {
3153     SetValueChanged(true);
3154   }
3155 
3156   UpdateAllValidityStates(true);
3157 }
3158 
3159 void
FireChangeEventIfNeeded()3160 HTMLInputElement::FireChangeEventIfNeeded()
3161 {
3162   nsAutoString value;
3163   GetValue(value);
3164 
3165   if (!MayFireChangeOnBlur() || mFocusedValue.Equals(value)) {
3166     return;
3167   }
3168 
3169   // Dispatch the change event.
3170   mFocusedValue = value;
3171   nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
3172                                        static_cast<nsIContent*>(this),
3173                                        NS_LITERAL_STRING("change"), true,
3174                                        false);
3175 }
3176 
3177 FileList*
GetFiles()3178 HTMLInputElement::GetFiles()
3179 {
3180   if (mType != NS_FORM_INPUT_FILE) {
3181     return nullptr;
3182   }
3183 
3184   if (Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs() &&
3185       (!Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) ||
3186        !HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
3187     return nullptr;
3188   }
3189 
3190   if (!mFileList) {
3191     mFileList = new FileList(static_cast<nsIContent*>(this));
3192     UpdateFileList();
3193   }
3194 
3195   return mFileList;
3196 }
3197 
3198 /* static */ void
HandleNumberControlSpin(void * aData)3199 HTMLInputElement::HandleNumberControlSpin(void* aData)
3200 {
3201   HTMLInputElement* input = static_cast<HTMLInputElement*>(aData);
3202 
3203   NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
3204                "Should have called nsRepeatService::Stop()");
3205 
3206   nsNumberControlFrame* numberControlFrame =
3207     do_QueryFrame(input->GetPrimaryFrame());
3208   if (input->mType != NS_FORM_INPUT_NUMBER || !numberControlFrame) {
3209     // Type has changed (and possibly our frame type hasn't been updated yet)
3210     // or else we've lost our frame. Either way, stop the timer and don't do
3211     // anything else.
3212     input->StopNumberControlSpinnerSpin();
3213   } else {
3214     input->StepNumberControlForUserEvent(input->mNumberControlSpinnerSpinsUp ? 1 : -1);
3215   }
3216 }
3217 
3218 void
UpdateFileList()3219 HTMLInputElement::UpdateFileList()
3220 {
3221   if (mFileList) {
3222     mFileList->Clear();
3223 
3224     const nsTArray<OwningFileOrDirectory>& array =
3225       GetFilesOrDirectoriesInternal();
3226 
3227     for (uint32_t i = 0; i < array.Length(); ++i) {
3228       if (array[i].IsFile()) {
3229         mFileList->Append(array[i].GetAsFile());
3230       }
3231     }
3232   }
3233 }
3234 
3235 nsresult
SetValueInternal(const nsAString & aValue,uint32_t aFlags)3236 HTMLInputElement::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
3237 {
3238   NS_PRECONDITION(GetValueMode() != VALUE_MODE_FILENAME,
3239                   "Don't call SetValueInternal for file inputs");
3240 
3241   switch (GetValueMode()) {
3242     case VALUE_MODE_VALUE:
3243     {
3244       // At the moment, only single line text control have to sanitize their value
3245       // Because we have to create a new string for that, we should prevent doing
3246       // it if it's useless.
3247       nsAutoString value(aValue);
3248 
3249       if (mDoneCreating) {
3250         SanitizeValue(value);
3251       }
3252       // else DoneCreatingElement calls us again once mDoneCreating is true
3253 
3254       bool setValueChanged = !!(aFlags & nsTextEditorState::eSetValue_Notify);
3255       if (setValueChanged) {
3256         SetValueChanged(true);
3257       }
3258 
3259       if (IsSingleLineTextControl(false)) {
3260         if (!mInputData.mState->SetValue(value, aFlags)) {
3261           return NS_ERROR_OUT_OF_MEMORY;
3262         }
3263         if (mType == NS_FORM_INPUT_EMAIL) {
3264           UpdateAllValidityStates(!mDoneCreating);
3265         }
3266       } else {
3267         free(mInputData.mValue);
3268         mInputData.mValue = ToNewUnicode(value);
3269         if (setValueChanged) {
3270           SetValueChanged(true);
3271         }
3272         if (mType == NS_FORM_INPUT_NUMBER) {
3273           // This has to happen before OnValueChanged is called because that
3274           // method needs the new value of our frame's anon text control.
3275           nsNumberControlFrame* numberControlFrame =
3276             do_QueryFrame(GetPrimaryFrame());
3277           if (numberControlFrame) {
3278             numberControlFrame->SetValueOfAnonTextControl(value);
3279           }
3280         } else if (mType == NS_FORM_INPUT_RANGE) {
3281           nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
3282           if (frame) {
3283             frame->UpdateForValueChange();
3284           }
3285         } else if (mType == NS_FORM_INPUT_TIME &&
3286                    !IsExperimentalMobileType(mType)) {
3287           nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
3288           if (frame) {
3289             frame->UpdateInputBoxValue();
3290           }
3291         }
3292         if (mDoneCreating) {
3293           OnValueChanged(/* aNotify = */ true,
3294                          /* aWasInteractiveUserChange = */ false);
3295         }
3296         // else DoneCreatingElement calls us again once mDoneCreating is true
3297       }
3298 
3299       if (mType == NS_FORM_INPUT_COLOR) {
3300         // Update color frame, to reflect color changes
3301         nsColorControlFrame* colorControlFrame = do_QueryFrame(GetPrimaryFrame());
3302         if (colorControlFrame) {
3303           colorControlFrame->UpdateColor();
3304         }
3305       }
3306 
3307       // This call might be useless in some situations because if the element is
3308       // a single line text control, nsTextEditorState::SetValue will call
3309       // nsHTMLInputElement::OnValueChanged which is going to call UpdateState()
3310       // if the element is focused. This bug 665547.
3311       if (PlaceholderApplies() &&
3312           HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
3313         UpdateState(true);
3314       }
3315 
3316       return NS_OK;
3317     }
3318 
3319     case VALUE_MODE_DEFAULT:
3320     case VALUE_MODE_DEFAULT_ON:
3321       // If the value of a hidden input was changed, we mark it changed so that we
3322       // will know we need to save / restore the value.  Yes, we are overloading
3323       // the meaning of ValueChanged just a teensy bit to save a measly byte of
3324       // storage space in HTMLInputElement.  Yes, you are free to make a new flag,
3325       // NEED_TO_SAVE_VALUE, at such time as mBitField becomes a 16-bit value.
3326       if (mType == NS_FORM_INPUT_HIDDEN) {
3327         SetValueChanged(true);
3328       }
3329 
3330       // Treat value == defaultValue for other input elements.
3331       return nsGenericHTMLFormElementWithState::SetAttr(kNameSpaceID_None,
3332                                                         nsGkAtoms::value, aValue,
3333                                                         true);
3334 
3335     case VALUE_MODE_FILENAME:
3336       return NS_ERROR_UNEXPECTED;
3337   }
3338 
3339   // This return statement is required for some compilers.
3340   return NS_OK;
3341 }
3342 
3343 NS_IMETHODIMP
SetValueChanged(bool aValueChanged)3344 HTMLInputElement::SetValueChanged(bool aValueChanged)
3345 {
3346   bool valueChangedBefore = mValueChanged;
3347 
3348   mValueChanged = aValueChanged;
3349 
3350   if (valueChangedBefore != aValueChanged) {
3351     UpdateState(true);
3352   }
3353 
3354   return NS_OK;
3355 }
3356 
3357 NS_IMETHODIMP
GetChecked(bool * aChecked)3358 HTMLInputElement::GetChecked(bool* aChecked)
3359 {
3360   *aChecked = Checked();
3361   return NS_OK;
3362 }
3363 
3364 void
SetCheckedChanged(bool aCheckedChanged)3365 HTMLInputElement::SetCheckedChanged(bool aCheckedChanged)
3366 {
3367   DoSetCheckedChanged(aCheckedChanged, true);
3368 }
3369 
3370 void
DoSetCheckedChanged(bool aCheckedChanged,bool aNotify)3371 HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged,
3372                                       bool aNotify)
3373 {
3374   if (mType == NS_FORM_INPUT_RADIO) {
3375     if (mCheckedChanged != aCheckedChanged) {
3376       nsCOMPtr<nsIRadioVisitor> visitor =
3377         new nsRadioSetCheckedChangedVisitor(aCheckedChanged);
3378       VisitGroup(visitor, aNotify);
3379     }
3380   } else {
3381     SetCheckedChangedInternal(aCheckedChanged);
3382   }
3383 }
3384 
3385 void
SetCheckedChangedInternal(bool aCheckedChanged)3386 HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged)
3387 {
3388   bool checkedChangedBefore = mCheckedChanged;
3389 
3390   mCheckedChanged = aCheckedChanged;
3391 
3392   // This method can't be called when we are not authorized to notify
3393   // so we do not need a aNotify parameter.
3394   if (checkedChangedBefore != aCheckedChanged) {
3395     UpdateState(true);
3396   }
3397 }
3398 
3399 NS_IMETHODIMP
SetChecked(bool aChecked)3400 HTMLInputElement::SetChecked(bool aChecked)
3401 {
3402   DoSetChecked(aChecked, true, true);
3403   return NS_OK;
3404 }
3405 
3406 void
DoSetChecked(bool aChecked,bool aNotify,bool aSetValueChanged)3407 HTMLInputElement::DoSetChecked(bool aChecked, bool aNotify,
3408                                bool aSetValueChanged)
3409 {
3410   // If the user or JS attempts to set checked, whether it actually changes the
3411   // value or not, we say the value was changed so that defaultValue don't
3412   // affect it no more.
3413   if (aSetValueChanged) {
3414     DoSetCheckedChanged(true, aNotify);
3415   }
3416 
3417   // Don't do anything if we're not changing whether it's checked (it would
3418   // screw up state actually, especially when you are setting radio button to
3419   // false)
3420   if (mChecked == aChecked) {
3421     return;
3422   }
3423 
3424   // Set checked
3425   if (mType != NS_FORM_INPUT_RADIO) {
3426     SetCheckedInternal(aChecked, aNotify);
3427     return;
3428   }
3429 
3430   // For radio button, we need to do some extra fun stuff
3431   if (aChecked) {
3432     RadioSetChecked(aNotify);
3433     return;
3434   }
3435 
3436   nsIRadioGroupContainer* container = GetRadioGroupContainer();
3437   if (container) {
3438     nsAutoString name;
3439     GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
3440     container->SetCurrentRadioButton(name, nullptr);
3441   }
3442   // SetCheckedInternal is going to ask all radios to update their
3443   // validity state. We have to be sure the radio group container knows
3444   // the currently selected radio.
3445   SetCheckedInternal(false, aNotify);
3446 }
3447 
3448 void
RadioSetChecked(bool aNotify)3449 HTMLInputElement::RadioSetChecked(bool aNotify)
3450 {
3451   // Find the selected radio button so we can deselect it
3452   nsCOMPtr<nsIDOMHTMLInputElement> currentlySelected = GetSelectedRadioButton();
3453 
3454   // Deselect the currently selected radio button
3455   if (currentlySelected) {
3456     // Pass true for the aNotify parameter since the currently selected
3457     // button is already in the document.
3458     static_cast<HTMLInputElement*>(currentlySelected.get())
3459       ->SetCheckedInternal(false, true);
3460   }
3461 
3462   // Let the group know that we are now the One True Radio Button
3463   nsIRadioGroupContainer* container = GetRadioGroupContainer();
3464   if (container) {
3465     nsAutoString name;
3466     GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
3467     container->SetCurrentRadioButton(name, this);
3468   }
3469 
3470   // SetCheckedInternal is going to ask all radios to update their
3471   // validity state.
3472   SetCheckedInternal(true, aNotify);
3473 }
3474 
3475 nsIRadioGroupContainer*
GetRadioGroupContainer() const3476 HTMLInputElement::GetRadioGroupContainer() const
3477 {
3478   NS_ASSERTION(mType == NS_FORM_INPUT_RADIO,
3479                "GetRadioGroupContainer should only be called when type='radio'");
3480 
3481   nsAutoString name;
3482   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
3483 
3484   if (name.IsEmpty()) {
3485     return nullptr;
3486   }
3487 
3488   if (mForm) {
3489     return mForm;
3490   }
3491 
3492   //XXXsmaug It isn't clear how this should work in Shadow DOM.
3493   return static_cast<nsDocument*>(GetUncomposedDoc());
3494 }
3495 
3496 already_AddRefed<nsIDOMHTMLInputElement>
GetSelectedRadioButton() const3497 HTMLInputElement::GetSelectedRadioButton() const
3498 {
3499   nsIRadioGroupContainer* container = GetRadioGroupContainer();
3500   if (!container) {
3501     return nullptr;
3502   }
3503 
3504   nsAutoString name;
3505   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
3506 
3507   nsCOMPtr<nsIDOMHTMLInputElement> selected = container->GetCurrentRadioButton(name);
3508   return selected.forget();
3509 }
3510 
3511 nsresult
MaybeSubmitForm(nsPresContext * aPresContext)3512 HTMLInputElement::MaybeSubmitForm(nsPresContext* aPresContext)
3513 {
3514   if (!mForm) {
3515     // Nothing to do here.
3516     return NS_OK;
3517   }
3518 
3519   nsCOMPtr<nsIPresShell> shell = aPresContext->GetPresShell();
3520   if (!shell) {
3521     return NS_OK;
3522   }
3523 
3524   // Get the default submit element
3525   nsIFormControl* submitControl = mForm->GetDefaultSubmitElement();
3526   if (submitControl) {
3527     nsCOMPtr<nsIContent> submitContent = do_QueryInterface(submitControl);
3528     NS_ASSERTION(submitContent, "Form control not implementing nsIContent?!");
3529     // Fire the button's onclick handler and let the button handle
3530     // submitting the form.
3531     WidgetMouseEvent event(true, eMouseClick, nullptr, WidgetMouseEvent::eReal);
3532     nsEventStatus status = nsEventStatus_eIgnore;
3533     shell->HandleDOMEventWithTarget(submitContent, &event, &status);
3534   } else if (!mForm->ImplicitSubmissionIsDisabled() &&
3535              mForm->SubmissionCanProceed(nullptr)) {
3536     // TODO: removing this code and have the submit event sent by the form,
3537     // bug 592124.
3538     // If there's only one text control, just submit the form
3539     // Hold strong ref across the event
3540     RefPtr<mozilla::dom::HTMLFormElement> form = mForm;
3541     InternalFormEvent event(true, eFormSubmit);
3542     nsEventStatus status = nsEventStatus_eIgnore;
3543     shell->HandleDOMEventWithTarget(form, &event, &status);
3544   }
3545 
3546   return NS_OK;
3547 }
3548 
3549 void
SetCheckedInternal(bool aChecked,bool aNotify)3550 HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify)
3551 {
3552   // Set the value
3553   mChecked = aChecked;
3554 
3555   // Notify the frame
3556   if (mType == NS_FORM_INPUT_CHECKBOX || mType == NS_FORM_INPUT_RADIO) {
3557     nsIFrame* frame = GetPrimaryFrame();
3558     if (frame) {
3559       frame->InvalidateFrameSubtree();
3560     }
3561   }
3562 
3563   UpdateAllValidityStates(aNotify);
3564 
3565   // Notify the document that the CSS :checked pseudoclass for this element
3566   // has changed state.
3567   UpdateState(aNotify);
3568 
3569   // Notify all radios in the group that value has changed, this is to let
3570   // radios to have the chance to update its states, e.g., :indeterminate.
3571   if (mType == NS_FORM_INPUT_RADIO) {
3572     nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
3573     VisitGroup(visitor, aNotify);
3574   }
3575 }
3576 
3577 void
Blur(ErrorResult & aError)3578 HTMLInputElement::Blur(ErrorResult& aError)
3579 {
3580   if (mType == NS_FORM_INPUT_NUMBER) {
3581     // Blur our anonymous text control, if we have one. (DOM 'change' event
3582     // firing and other things depend on this.)
3583     nsNumberControlFrame* numberControlFrame =
3584       do_QueryFrame(GetPrimaryFrame());
3585     if (numberControlFrame) {
3586       HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
3587       if (textControl) {
3588         textControl->Blur(aError);
3589         return;
3590       }
3591     }
3592   }
3593 
3594   if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
3595     nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
3596     if (frame) {
3597       frame->HandleBlurEvent();
3598       return;
3599     }
3600   }
3601 
3602   nsGenericHTMLElement::Blur(aError);
3603 }
3604 
3605 void
Focus(ErrorResult & aError)3606 HTMLInputElement::Focus(ErrorResult& aError)
3607 {
3608   if (mType == NS_FORM_INPUT_NUMBER) {
3609     // Focus our anonymous text control, if we have one.
3610     nsNumberControlFrame* numberControlFrame =
3611       do_QueryFrame(GetPrimaryFrame());
3612     if (numberControlFrame) {
3613       HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
3614       if (textControl) {
3615         textControl->Focus(aError);
3616         return;
3617       }
3618     }
3619   }
3620 
3621   if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
3622     nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
3623     if (frame) {
3624       frame->HandleFocusEvent();
3625       return;
3626     }
3627   }
3628 
3629   if (mType != NS_FORM_INPUT_FILE) {
3630     nsGenericHTMLElement::Focus(aError);
3631     return;
3632   }
3633 
3634   // For file inputs, focus the first button instead. In the case of there
3635   // being two buttons (when the picker is a directory picker) the user can
3636   // tab to the next one.
3637   nsIFrame* frame = GetPrimaryFrame();
3638   if (frame) {
3639     for (nsIFrame* childFrame : frame->PrincipalChildList()) {
3640       // See if the child is a button control.
3641       nsCOMPtr<nsIFormControl> formCtrl =
3642         do_QueryInterface(childFrame->GetContent());
3643       if (formCtrl && formCtrl->GetType() == NS_FORM_BUTTON_BUTTON) {
3644         nsCOMPtr<nsIDOMElement> element = do_QueryInterface(formCtrl);
3645         nsIFocusManager* fm = nsFocusManager::GetFocusManager();
3646         if (fm && element) {
3647           fm->SetFocus(element, 0);
3648         }
3649         break;
3650       }
3651     }
3652   }
3653 
3654   return;
3655 }
3656 
3657 #if !defined(ANDROID) && !defined(XP_MACOSX)
3658 bool
IsNodeApzAwareInternal() const3659 HTMLInputElement::IsNodeApzAwareInternal() const
3660 {
3661   // Tell APZC we may handle mouse wheel event and do preventDefault when input
3662   // type is number.
3663   return (mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE) ||
3664          nsINode::IsNodeApzAwareInternal();
3665 }
3666 #endif
3667 
3668 bool
IsInteractiveHTMLContent(bool aIgnoreTabindex) const3669 HTMLInputElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
3670 {
3671   return mType != NS_FORM_INPUT_HIDDEN ||
3672          nsGenericHTMLFormElementWithState::IsInteractiveHTMLContent(aIgnoreTabindex);
3673 }
3674 
3675 void
AsyncEventRunning(AsyncEventDispatcher * aEvent)3676 HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
3677 {
3678   nsImageLoadingContent::AsyncEventRunning(aEvent);
3679 }
3680 
3681 NS_IMETHODIMP
Select()3682 HTMLInputElement::Select()
3683 {
3684   if (mType == NS_FORM_INPUT_NUMBER) {
3685     nsNumberControlFrame* numberControlFrame =
3686       do_QueryFrame(GetPrimaryFrame());
3687     if (numberControlFrame) {
3688       return numberControlFrame->HandleSelectCall();
3689     }
3690     return NS_OK;
3691   }
3692 
3693   if (!IsSingleLineTextControl(false)) {
3694     return NS_OK;
3695   }
3696 
3697   // XXX Bug?  We have to give the input focus before contents can be
3698   // selected
3699 
3700   FocusTristate state = FocusState();
3701   if (state == eUnfocusable) {
3702     return NS_OK;
3703   }
3704 
3705   nsTextEditorState* tes = GetEditorState();
3706   if (tes) {
3707     RefPtr<nsFrameSelection> fs = tes->GetConstFrameSelection();
3708     if (fs && fs->MouseDownRecorded()) {
3709       // This means that we're being called while the frame selection has a mouse
3710       // down event recorded to adjust the caret during the mouse up event.
3711       // We are probably called from the focus event handler.  We should override
3712       // the delayed caret data in this case to ensure that this select() call
3713       // takes effect.
3714       fs->SetDelayedCaretData(nullptr);
3715     }
3716   }
3717 
3718   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
3719 
3720   RefPtr<nsPresContext> presContext = GetPresContext(eForComposedDoc);
3721   if (state == eInactiveWindow) {
3722     if (fm)
3723       fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
3724     SelectAll(presContext);
3725     return NS_OK;
3726   }
3727 
3728   if (DispatchSelectEvent(presContext) && fm) {
3729     fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
3730 
3731     // ensure that the element is actually focused
3732     nsCOMPtr<nsIDOMElement> focusedElement;
3733     fm->GetFocusedElement(getter_AddRefs(focusedElement));
3734     if (SameCOMIdentity(static_cast<nsIDOMNode*>(this), focusedElement)) {
3735       // Now Select all the text!
3736       SelectAll(presContext);
3737     }
3738   }
3739 
3740   return NS_OK;
3741 }
3742 
3743 bool
DispatchSelectEvent(nsPresContext * aPresContext)3744 HTMLInputElement::DispatchSelectEvent(nsPresContext* aPresContext)
3745 {
3746   nsEventStatus status = nsEventStatus_eIgnore;
3747 
3748   // If already handling select event, don't dispatch a second.
3749   if (!mHandlingSelectEvent) {
3750     WidgetEvent event(nsContentUtils::LegacyIsCallerChromeOrNativeCode(), eFormSelect);
3751 
3752     mHandlingSelectEvent = true;
3753     EventDispatcher::Dispatch(static_cast<nsIContent*>(this),
3754                               aPresContext, &event, nullptr, &status);
3755     mHandlingSelectEvent = false;
3756   }
3757 
3758   // If the DOM event was not canceled (e.g. by a JS event handler
3759   // returning false)
3760   return (status == nsEventStatus_eIgnore);
3761 }
3762 
3763 void
SelectAll(nsPresContext * aPresContext)3764 HTMLInputElement::SelectAll(nsPresContext* aPresContext)
3765 {
3766   nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
3767 
3768   if (formControlFrame) {
3769     formControlFrame->SetFormProperty(nsGkAtoms::select, EmptyString());
3770   }
3771 }
3772 
3773 bool
NeedToInitializeEditorForEvent(EventChainPreVisitor & aVisitor) const3774 HTMLInputElement::NeedToInitializeEditorForEvent(
3775                     EventChainPreVisitor& aVisitor) const
3776 {
3777   // We only need to initialize the editor for single line input controls because they
3778   // are lazily initialized.  We don't need to initialize the control for
3779   // certain types of events, because we know that those events are safe to be
3780   // handled without the editor being initialized.  These events include:
3781   // mousein/move/out, overflow/underflow, and DOM mutation events.
3782   if (!IsSingleLineTextControl(false) ||
3783       aVisitor.mEvent->mClass == eMutationEventClass) {
3784     return false;
3785   }
3786 
3787   switch (aVisitor.mEvent->mMessage) {
3788   case eMouseMove:
3789   case eMouseEnterIntoWidget:
3790   case eMouseExitFromWidget:
3791   case eMouseOver:
3792   case eMouseOut:
3793   case eScrollPortUnderflow:
3794   case eScrollPortOverflow:
3795     return false;
3796   default:
3797     return true;
3798   }
3799 }
3800 
3801 bool
IsDisabledForEvents(EventMessage aMessage)3802 HTMLInputElement::IsDisabledForEvents(EventMessage aMessage)
3803 {
3804   return IsElementDisabledForEvents(aMessage, GetPrimaryFrame());
3805 }
3806 
3807 nsresult
PreHandleEvent(EventChainPreVisitor & aVisitor)3808 HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
3809 {
3810   // Do not process any DOM events if the element is disabled
3811   aVisitor.mCanHandle = false;
3812   if (IsDisabledForEvents(aVisitor.mEvent->mMessage)) {
3813     return NS_OK;
3814   }
3815 
3816   // Initialize the editor if needed.
3817   if (NeedToInitializeEditorForEvent(aVisitor)) {
3818     nsITextControlFrame* textControlFrame = do_QueryFrame(GetPrimaryFrame());
3819     if (textControlFrame)
3820       textControlFrame->EnsureEditorInitialized();
3821   }
3822 
3823   //FIXME Allow submission etc. also when there is no prescontext, Bug 329509.
3824   if (!aVisitor.mPresContext) {
3825     return nsGenericHTMLElement::PreHandleEvent(aVisitor);
3826   }
3827   //
3828   // Web pages expect the value of a radio button or checkbox to be set
3829   // *before* onclick and DOMActivate fire, and they expect that if they set
3830   // the value explicitly during onclick or DOMActivate it will not be toggled
3831   // or any such nonsense.
3832   // In order to support that (bug 57137 and 58460 are examples) we toggle
3833   // the checked attribute *first*, and then fire onclick.  If the user
3834   // returns false, we reset the control to the old checked value.  Otherwise,
3835   // we dispatch DOMActivate.  If DOMActivate is cancelled, we also reset
3836   // the control to the old checked value.  We need to keep track of whether
3837   // we've already toggled the state from onclick since the user could
3838   // explicitly dispatch DOMActivate on the element.
3839   //
3840   // This is a compatibility hack.
3841   //
3842 
3843   // Track whether we're in the outermost Dispatch invocation that will
3844   // cause activation of the input.  That is, if we're a click event, or a
3845   // DOMActivate that was dispatched directly, this will be set, but if we're
3846   // a DOMActivate dispatched from click handling, it will not be set.
3847   WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3848   bool outerActivateEvent =
3849     ((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
3850      (aVisitor.mEvent->mMessage == eLegacyDOMActivate && !mInInternalActivate));
3851 
3852   if (outerActivateEvent) {
3853     aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
3854   }
3855 
3856   bool originalCheckedValue = false;
3857 
3858   if (outerActivateEvent) {
3859     mCheckedIsToggled = false;
3860 
3861     switch(mType) {
3862       case NS_FORM_INPUT_CHECKBOX:
3863         {
3864           if (mIndeterminate) {
3865             // indeterminate is always set to FALSE when the checkbox is toggled
3866             SetIndeterminateInternal(false, false);
3867             aVisitor.mItemFlags |= NS_ORIGINAL_INDETERMINATE_VALUE;
3868           }
3869 
3870           GetChecked(&originalCheckedValue);
3871           DoSetChecked(!originalCheckedValue, true, true);
3872           mCheckedIsToggled = true;
3873         }
3874         break;
3875 
3876       case NS_FORM_INPUT_RADIO:
3877         {
3878           nsCOMPtr<nsIDOMHTMLInputElement> selectedRadioButton = GetSelectedRadioButton();
3879           aVisitor.mItemData = selectedRadioButton;
3880 
3881           originalCheckedValue = mChecked;
3882           if (!originalCheckedValue) {
3883             DoSetChecked(true, true, true);
3884             mCheckedIsToggled = true;
3885           }
3886         }
3887         break;
3888 
3889       case NS_FORM_INPUT_SUBMIT:
3890       case NS_FORM_INPUT_IMAGE:
3891         if (mForm) {
3892           // tell the form that we are about to enter a click handler.
3893           // that means that if there are scripted submissions, the
3894           // latest one will be deferred until after the exit point of the handler.
3895           mForm->OnSubmitClickBegin(this);
3896         }
3897         break;
3898 
3899       default:
3900         break;
3901     }
3902   }
3903 
3904   if (originalCheckedValue) {
3905     aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
3906   }
3907 
3908   // If mNoContentDispatch is true we will not allow content to handle
3909   // this event.  But to allow middle mouse button paste to work we must allow
3910   // middle clicks to go to text fields anyway.
3911   if (aVisitor.mEvent->mFlags.mNoContentDispatch) {
3912     aVisitor.mItemFlags |= NS_NO_CONTENT_DISPATCH;
3913   }
3914   if (IsSingleLineTextControl(false) &&
3915       aVisitor.mEvent->mMessage == eMouseClick &&
3916       aVisitor.mEvent->AsMouseEvent()->button ==
3917         WidgetMouseEvent::eMiddleButton) {
3918     aVisitor.mEvent->mFlags.mNoContentDispatch = false;
3919   }
3920 
3921   // We must cache type because mType may change during JS event (bug 2369)
3922   aVisitor.mItemFlags |= mType;
3923 
3924   if (aVisitor.mEvent->mMessage == eFocus &&
3925       aVisitor.mEvent->IsTrusted() &&
3926       MayFireChangeOnBlur() &&
3927       // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
3928       // we get the 'focus' event.
3929       !mIsDraggingRange) {
3930     GetValue(mFocusedValue);
3931   }
3932 
3933   // Fire onchange (if necessary), before we do the blur, bug 357684.
3934   if (aVisitor.mEvent->mMessage == eBlur) {
3935     // Experimental mobile types rely on the system UI to prevent users to not
3936     // set invalid values but we have to be extra-careful. Especially if the
3937     // option has been enabled on desktop.
3938     if (IsExperimentalMobileType(mType)) {
3939       nsAutoString aValue;
3940       GetValueInternal(aValue);
3941       nsresult rv =
3942         SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
3943       NS_ENSURE_SUCCESS(rv, rv);
3944     }
3945     FireChangeEventIfNeeded();
3946   }
3947 
3948   if (mType == NS_FORM_INPUT_RANGE &&
3949       (aVisitor.mEvent->mMessage == eFocus ||
3950        aVisitor.mEvent->mMessage == eBlur)) {
3951     // Just as nsGenericHTMLFormElementWithState::PreHandleEvent calls
3952     // nsIFormControlFrame::SetFocus, we handle focus here.
3953     nsIFrame* frame = GetPrimaryFrame();
3954     if (frame) {
3955       frame->InvalidateFrameSubtree();
3956     }
3957   }
3958 
3959   if (mType == NS_FORM_INPUT_TIME &&
3960       !IsExperimentalMobileType(mType) &&
3961       aVisitor.mEvent->mMessage == eFocus &&
3962       aVisitor.mEvent->mOriginalTarget == this) {
3963     // If original target is this and not the anonymous text control, we should
3964     // pass the focus to the anonymous text control.
3965     nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
3966     if (frame) {
3967       frame->HandleFocusEvent();
3968     }
3969   }
3970 
3971   if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
3972     if (mNumberControlSpinnerIsSpinning) {
3973       // If the timer is running the user has depressed the mouse on one of the
3974       // spin buttons. If the mouse exits the button we either want to reverse
3975       // the direction of spin if it has moved over the other button, or else
3976       // we want to end the spin. We do this here (rather than in
3977       // PostHandleEvent) because we don't want to let content preventDefault()
3978       // the end of the spin.
3979       if (aVisitor.mEvent->mMessage == eMouseMove) {
3980         // Be aggressive about stopping the spin:
3981         bool stopSpin = true;
3982         nsNumberControlFrame* numberControlFrame =
3983           do_QueryFrame(GetPrimaryFrame());
3984         if (numberControlFrame) {
3985           bool oldNumberControlSpinTimerSpinsUpValue =
3986                  mNumberControlSpinnerSpinsUp;
3987           switch (numberControlFrame->GetSpinButtonForPointerEvent(
3988                     aVisitor.mEvent->AsMouseEvent())) {
3989           case nsNumberControlFrame::eSpinButtonUp:
3990             mNumberControlSpinnerSpinsUp = true;
3991             stopSpin = false;
3992             break;
3993           case nsNumberControlFrame::eSpinButtonDown:
3994             mNumberControlSpinnerSpinsUp = false;
3995             stopSpin = false;
3996             break;
3997           }
3998           if (mNumberControlSpinnerSpinsUp !=
3999                 oldNumberControlSpinTimerSpinsUpValue) {
4000             nsNumberControlFrame* numberControlFrame =
4001               do_QueryFrame(GetPrimaryFrame());
4002             if (numberControlFrame) {
4003               numberControlFrame->SpinnerStateChanged();
4004             }
4005           }
4006         }
4007         if (stopSpin) {
4008           StopNumberControlSpinnerSpin();
4009         }
4010       } else if (aVisitor.mEvent->mMessage == eMouseUp) {
4011         StopNumberControlSpinnerSpin();
4012       }
4013     }
4014     if (aVisitor.mEvent->mMessage == eFocus ||
4015         aVisitor.mEvent->mMessage == eBlur) {
4016       if (aVisitor.mEvent->mMessage == eFocus) {
4017         // Tell our frame it's getting focus so that it can make sure focus
4018         // is moved to our anonymous text control.
4019         nsNumberControlFrame* numberControlFrame =
4020           do_QueryFrame(GetPrimaryFrame());
4021         if (numberControlFrame) {
4022           // This could kill the frame!
4023           numberControlFrame->HandleFocusEvent(aVisitor.mEvent);
4024         }
4025       }
4026       nsIFrame* frame = GetPrimaryFrame();
4027       if (frame && frame->IsThemed()) {
4028         // Our frame's nested <input type=text> will be invalidated when it
4029         // loses focus, but since we are also native themed we need to make
4030         // sure that our entire area is repainted since any focus highlight
4031         // from the theme should be removed from us (the repainting of the
4032         // sub-area occupied by the anon text control is not enough to do
4033         // that).
4034         frame->InvalidateFrame();
4035       }
4036     }
4037   }
4038 
4039   nsresult rv = nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
4040 
4041   // We do this after calling the base class' PreHandleEvent so that
4042   // nsIContent::PreHandleEvent doesn't reset any change we make to mCanHandle.
4043   if (mType == NS_FORM_INPUT_NUMBER &&
4044       aVisitor.mEvent->IsTrusted()  &&
4045       aVisitor.mEvent->mOriginalTarget != this) {
4046     // <input type=number> has an anonymous <input type=text> descendant. If
4047     // 'input' or 'change' events are fired at that text control then we need
4048     // to do some special handling here.
4049     HTMLInputElement* textControl = nullptr;
4050     nsNumberControlFrame* numberControlFrame =
4051       do_QueryFrame(GetPrimaryFrame());
4052     if (numberControlFrame) {
4053       textControl = numberControlFrame->GetAnonTextControl();
4054     }
4055     if (textControl && aVisitor.mEvent->mOriginalTarget == textControl) {
4056       if (aVisitor.mEvent->mMessage == eEditorInput) {
4057         // Propogate the anon text control's new value to our HTMLInputElement:
4058         nsAutoString value;
4059         numberControlFrame->GetValueOfAnonTextControl(value);
4060         numberControlFrame->HandlingInputEvent(true);
4061         nsWeakFrame weakNumberControlFrame(numberControlFrame);
4062         rv = SetValueInternal(value,
4063                               nsTextEditorState::eSetValue_BySetUserInput |
4064                               nsTextEditorState::eSetValue_Notify);
4065         NS_ENSURE_SUCCESS(rv, rv);
4066         if (weakNumberControlFrame.IsAlive()) {
4067           numberControlFrame->HandlingInputEvent(false);
4068         }
4069       }
4070       else if (aVisitor.mEvent->mMessage == eFormChange) {
4071         // We cancel the DOM 'change' event that is fired for any change to our
4072         // anonymous text control since we fire our own 'change' events and
4073         // content shouldn't be seeing two 'change' events. Besides that we
4074         // (as a number) control have tighter restrictions on when our internal
4075         // value changes than our anon text control does, so in some cases
4076         // (if our text control's value doesn't parse as a number) we don't
4077         // want to fire a 'change' event at all.
4078         aVisitor.mCanHandle = false;
4079       }
4080     }
4081   }
4082 
4083   // Stop the event if the related target's first non-native ancestor is the
4084   // same as the original target's first non-native ancestor (we are moving
4085   // inside of the same element).
4086   if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType) &&
4087       (aVisitor.mEvent->mMessage == eFocus ||
4088        aVisitor.mEvent->mMessage == eFocusIn ||
4089        aVisitor.mEvent->mMessage == eFocusOut ||
4090        aVisitor.mEvent->mMessage == eBlur)) {
4091     nsCOMPtr<nsIContent> originalTarget =
4092       do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
4093     nsCOMPtr<nsIContent> relatedTarget =
4094       do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
4095 
4096     if (originalTarget && relatedTarget &&
4097         originalTarget->FindFirstNonChromeOnlyAccessContent() ==
4098         relatedTarget->FindFirstNonChromeOnlyAccessContent()) {
4099       aVisitor.mCanHandle = false;
4100     }
4101   }
4102 
4103   return rv;
4104 }
4105 
4106 void
StartRangeThumbDrag(WidgetGUIEvent * aEvent)4107 HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent)
4108 {
4109   mIsDraggingRange = true;
4110   mRangeThumbDragStartValue = GetValueAsDecimal();
4111   // Don't use CAPTURE_RETARGETTOELEMENT, as that breaks pseudo-class styling
4112   // of the thumb.
4113   nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
4114   nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4115 
4116   // Before we change the value, record the current value so that we'll
4117   // correctly send a 'change' event if appropriate. We need to do this here
4118   // because the 'focus' event is handled after the 'mousedown' event that
4119   // we're being called for (i.e. too late to update mFocusedValue, since we'll
4120   // have changed it by then).
4121   GetValue(mFocusedValue);
4122 
4123   SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
4124 }
4125 
4126 void
FinishRangeThumbDrag(WidgetGUIEvent * aEvent)4127 HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent* aEvent)
4128 {
4129   MOZ_ASSERT(mIsDraggingRange);
4130 
4131   if (nsIPresShell::GetCapturingContent() == this) {
4132     nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
4133   }
4134   if (aEvent) {
4135     nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4136     SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
4137   }
4138   mIsDraggingRange = false;
4139   FireChangeEventIfNeeded();
4140 }
4141 
4142 void
CancelRangeThumbDrag(bool aIsForUserEvent)4143 HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent)
4144 {
4145   MOZ_ASSERT(mIsDraggingRange);
4146 
4147   mIsDraggingRange = false;
4148   if (nsIPresShell::GetCapturingContent() == this) {
4149     nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
4150   }
4151   if (aIsForUserEvent) {
4152     SetValueOfRangeForUserEvent(mRangeThumbDragStartValue);
4153   } else {
4154     // Don't dispatch an 'input' event - at least not using
4155     // DispatchTrustedEvent.
4156     // TODO: decide what we should do here - bug 851782.
4157     nsAutoString val;
4158     ConvertNumberToString(mRangeThumbDragStartValue, val);
4159     // TODO: What should we do if SetValueInternal fails?  (The allocation
4160     // is small, so we should be fine here.)
4161     SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
4162                           nsTextEditorState::eSetValue_Notify);
4163     nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
4164     if (frame) {
4165       frame->UpdateForValueChange();
4166     }
4167     RefPtr<AsyncEventDispatcher> asyncDispatcher =
4168       new AsyncEventDispatcher(this, NS_LITERAL_STRING("input"), true, false);
4169     asyncDispatcher->RunDOMEventWhenSafe();
4170   }
4171 }
4172 
4173 void
SetValueOfRangeForUserEvent(Decimal aValue)4174 HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
4175 {
4176   MOZ_ASSERT(aValue.isFinite());
4177 
4178   Decimal oldValue = GetValueAsDecimal();
4179 
4180   nsAutoString val;
4181   ConvertNumberToString(aValue, val);
4182   // TODO: What should we do if SetValueInternal fails?  (The allocation
4183   // is small, so we should be fine here.)
4184   SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
4185                         nsTextEditorState::eSetValue_Notify);
4186   nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
4187   if (frame) {
4188     frame->UpdateForValueChange();
4189   }
4190 
4191   if (GetValueAsDecimal() != oldValue) {
4192     nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
4193                                          static_cast<nsIDOMHTMLInputElement*>(this),
4194                                          NS_LITERAL_STRING("input"), true,
4195                                          false);
4196   }
4197 }
4198 
4199 void
StartNumberControlSpinnerSpin()4200 HTMLInputElement::StartNumberControlSpinnerSpin()
4201 {
4202   MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
4203 
4204   mNumberControlSpinnerIsSpinning = true;
4205 
4206   nsRepeatService::GetInstance()->Start(HandleNumberControlSpin, this);
4207 
4208   // Capture the mouse so that we can tell if the pointer moves from one
4209   // spin button to the other, or to some other element:
4210   nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
4211 
4212   nsNumberControlFrame* numberControlFrame =
4213     do_QueryFrame(GetPrimaryFrame());
4214   if (numberControlFrame) {
4215     numberControlFrame->SpinnerStateChanged();
4216   }
4217 }
4218 
4219 void
StopNumberControlSpinnerSpin(SpinnerStopState aState)4220 HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState)
4221 {
4222   if (mNumberControlSpinnerIsSpinning) {
4223     if (nsIPresShell::GetCapturingContent() == this) {
4224       nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
4225     }
4226 
4227     nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
4228 
4229     mNumberControlSpinnerIsSpinning = false;
4230 
4231     if (aState == eAllowDispatchingEvents) {
4232       FireChangeEventIfNeeded();
4233     }
4234 
4235     nsNumberControlFrame* numberControlFrame =
4236       do_QueryFrame(GetPrimaryFrame());
4237     if (numberControlFrame) {
4238       MOZ_ASSERT(aState == eAllowDispatchingEvents,
4239                  "Shouldn't have primary frame for the element when we're not "
4240                  "allowed to dispatch events to it anymore.");
4241       numberControlFrame->SpinnerStateChanged();
4242     }
4243   }
4244 }
4245 
4246 void
StepNumberControlForUserEvent(int32_t aDirection)4247 HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection)
4248 {
4249   // We can't use GetValidityState here because the validity state is not set
4250   // if the user hasn't previously taken an action to set or change the value,
4251   // according to the specs.
4252   if (HasBadInput()) {
4253     // If the user has typed a value into the control and inadvertently made a
4254     // mistake (e.g. put a thousand separator at the wrong point) we do not
4255     // want to wipe out what they typed if they try to increment/decrement the
4256     // value. Better is to highlight the value as being invalid so that they
4257     // can correct what they typed.
4258     // We only do this if there actually is a value typed in by/displayed to
4259     // the user. (IsValid() can return false if the 'required' attribute is
4260     // set and the value is the empty string.)
4261     nsNumberControlFrame* numberControlFrame =
4262       do_QueryFrame(GetPrimaryFrame());
4263     if (numberControlFrame &&
4264         !numberControlFrame->AnonTextControlIsEmpty()) {
4265       // We pass 'true' for UpdateValidityUIBits' aIsFocused argument
4266       // regardless because we need the UI to update _now_ or the user will
4267       // wonder why the step behavior isn't functioning.
4268       UpdateValidityUIBits(true);
4269       UpdateState(true);
4270       return;
4271     }
4272   }
4273 
4274   Decimal newValue = Decimal::nan(); // unchanged if value will not change
4275 
4276   nsresult rv = GetValueIfStepped(aDirection, CALLED_FOR_USER_EVENT, &newValue);
4277 
4278   if (NS_FAILED(rv) || !newValue.isFinite()) {
4279     return; // value should not or will not change
4280   }
4281 
4282   nsAutoString newVal;
4283   ConvertNumberToString(newValue, newVal);
4284   // TODO: What should we do if SetValueInternal fails?  (The allocation
4285   // is small, so we should be fine here.)
4286   SetValueInternal(newVal, nsTextEditorState::eSetValue_BySetUserInput |
4287                            nsTextEditorState::eSetValue_Notify);
4288 
4289   nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
4290                                        static_cast<nsIDOMHTMLInputElement*>(this),
4291                                        NS_LITERAL_STRING("input"), true,
4292                                        false);
4293 }
4294 
4295 static bool
SelectTextFieldOnFocus()4296 SelectTextFieldOnFocus()
4297 {
4298   if (!gSelectTextFieldOnFocus) {
4299     int32_t selectTextfieldsOnKeyFocus = -1;
4300     nsresult rv =
4301       LookAndFeel::GetInt(LookAndFeel::eIntID_SelectTextfieldsOnKeyFocus,
4302                           &selectTextfieldsOnKeyFocus);
4303     if (NS_FAILED(rv)) {
4304       gSelectTextFieldOnFocus = -1;
4305     } else {
4306       gSelectTextFieldOnFocus = selectTextfieldsOnKeyFocus != 0 ? 1 : -1;
4307     }
4308   }
4309 
4310   return gSelectTextFieldOnFocus == 1;
4311 }
4312 
4313 bool
ShouldPreventDOMActivateDispatch(EventTarget * aOriginalTarget)4314 HTMLInputElement::ShouldPreventDOMActivateDispatch(EventTarget* aOriginalTarget)
4315 {
4316   /*
4317    * For the moment, there is only one situation where we actually want to
4318    * prevent firing a DOMActivate event:
4319    *  - we are a <input type='file'> that just got a click event,
4320    *  - the event was targeted to our button which should have sent a
4321    *    DOMActivate event.
4322    */
4323 
4324   if (mType != NS_FORM_INPUT_FILE) {
4325     return false;
4326   }
4327 
4328   nsCOMPtr<nsIContent> target = do_QueryInterface(aOriginalTarget);
4329   if (!target) {
4330     return false;
4331   }
4332 
4333   return target->GetParent() == this &&
4334          target->IsRootOfNativeAnonymousSubtree() &&
4335          target->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
4336                              nsGkAtoms::button, eCaseMatters);
4337 }
4338 
4339 nsresult
MaybeInitPickers(EventChainPostVisitor & aVisitor)4340 HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor)
4341 {
4342   // Open a file picker when we receive a click on a <input type='file'>, or
4343   // open a color picker when we receive a click on a <input type='color'>.
4344   // A click is handled in the following cases:
4345   // - preventDefault() has not been called (or something similar);
4346   // - it's the left mouse button.
4347   // We do not prevent non-trusted click because authors can already use
4348   // .click(). However, the pickers will follow the rules of popup-blocking.
4349   if (aVisitor.mEvent->DefaultPrevented()) {
4350     return NS_OK;
4351   }
4352   WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
4353   if (!(mouseEvent && mouseEvent->IsLeftClickEvent())) {
4354     return NS_OK;
4355   }
4356   if (mType == NS_FORM_INPUT_FILE) {
4357     // If the user clicked on the "Choose folder..." button we open the
4358     // directory picker, else we open the file picker.
4359     FilePickerType type = FILE_PICKER_FILE;
4360     nsCOMPtr<nsIContent> target =
4361       do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
4362     if (target &&
4363         target->FindFirstNonChromeOnlyAccessContent() == this &&
4364         ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
4365          (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
4366           HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)))) {
4367       type = FILE_PICKER_DIRECTORY;
4368     }
4369     return InitFilePicker(type);
4370   }
4371   if (mType == NS_FORM_INPUT_COLOR) {
4372     return InitColorPicker();
4373   }
4374   if (mType == NS_FORM_INPUT_DATE) {
4375     return InitDatePicker();
4376   }
4377 
4378   return NS_OK;
4379 }
4380 
4381 /**
4382  * Return true if the input event should be ignore because of it's modifiers
4383  */
4384 static bool
IgnoreInputEventWithModifier(WidgetInputEvent * aEvent)4385 IgnoreInputEventWithModifier(WidgetInputEvent* aEvent)
4386 {
4387   return aEvent->IsShift() || aEvent->IsControl() || aEvent->IsAlt() ||
4388          aEvent->IsMeta() || aEvent->IsAltGraph() || aEvent->IsFn() ||
4389          aEvent->IsOS();
4390 }
4391 
4392 nsresult
PostHandleEvent(EventChainPostVisitor & aVisitor)4393 HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
4394 {
4395   if (!aVisitor.mPresContext) {
4396     // Hack alert! In order to open file picker even in case the element isn't
4397     // in document, try to init picker even without PresContext.
4398     return MaybeInitPickers(aVisitor);
4399   }
4400 
4401   if (aVisitor.mEvent->mMessage == eFocus ||
4402       aVisitor.mEvent->mMessage == eBlur) {
4403     if (aVisitor.mEvent->mMessage == eBlur) {
4404       if (mIsDraggingRange) {
4405         FinishRangeThumbDrag();
4406       } else if (mNumberControlSpinnerIsSpinning) {
4407         StopNumberControlSpinnerSpin();
4408       }
4409     }
4410 
4411     UpdateValidityUIBits(aVisitor.mEvent->mMessage == eFocus);
4412 
4413     UpdateState(true);
4414   }
4415 
4416   nsresult rv = NS_OK;
4417   bool outerActivateEvent = !!(aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT);
4418   bool originalCheckedValue =
4419     !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
4420   bool noContentDispatch = !!(aVisitor.mItemFlags & NS_NO_CONTENT_DISPATCH);
4421   uint8_t oldType = NS_CONTROL_TYPE(aVisitor.mItemFlags);
4422 
4423   // Ideally we would make the default action for click and space just dispatch
4424   // DOMActivate, and the default action for DOMActivate flip the checkbox/
4425   // radio state and fire onchange.  However, for backwards compatibility, we
4426   // need to flip the state before firing click, and we need to fire click
4427   // when space is pressed.  So, we just nest the firing of DOMActivate inside
4428   // the click event handling, and allow cancellation of DOMActivate to cancel
4429   // the click.
4430   if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault &&
4431       !IsSingleLineTextControl(true) &&
4432       mType != NS_FORM_INPUT_NUMBER) {
4433     WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
4434     if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
4435         !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->mOriginalTarget)) {
4436       // DOMActive event should be trusted since the activation is actually
4437       // occurred even if the cause is an untrusted click event.
4438       InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
4439       actEvent.mDetail = 1;
4440 
4441       nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
4442       if (shell) {
4443         nsEventStatus status = nsEventStatus_eIgnore;
4444         mInInternalActivate = true;
4445         rv = shell->HandleDOMEventWithTarget(this, &actEvent, &status);
4446         mInInternalActivate = false;
4447 
4448         // If activate is cancelled, we must do the same as when click is
4449         // cancelled (revert the checkbox to its original value).
4450         if (status == nsEventStatus_eConsumeNoDefault) {
4451           aVisitor.mEventStatus = status;
4452         }
4453       }
4454     }
4455   }
4456 
4457   if (outerActivateEvent) {
4458     switch(oldType) {
4459       case NS_FORM_INPUT_SUBMIT:
4460       case NS_FORM_INPUT_IMAGE:
4461         if (mForm) {
4462           // tell the form that we are about to exit a click handler
4463           // so the form knows not to defer subsequent submissions
4464           // the pending ones that were created during the handler
4465           // will be flushed or forgoten.
4466           mForm->OnSubmitClickEnd();
4467         }
4468         break;
4469       default:
4470         break;
4471     }
4472   }
4473 
4474   // Reset the flag for other content besides this text field
4475   aVisitor.mEvent->mFlags.mNoContentDispatch = noContentDispatch;
4476 
4477   // now check to see if the event was "cancelled"
4478   if (mCheckedIsToggled && outerActivateEvent) {
4479     if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
4480       // if it was cancelled and a radio button, then set the old
4481       // selected btn to TRUE. if it is a checkbox then set it to its
4482       // original value
4483       if (oldType == NS_FORM_INPUT_RADIO) {
4484         nsCOMPtr<nsIDOMHTMLInputElement> selectedRadioButton =
4485           do_QueryInterface(aVisitor.mItemData);
4486         if (selectedRadioButton) {
4487           selectedRadioButton->SetChecked(true);
4488         }
4489         // If there was no checked radio button or this one is no longer a
4490         // radio button we must reset it back to false to cancel the action.
4491         // See how the web of hack grows?
4492         if (!selectedRadioButton || mType != NS_FORM_INPUT_RADIO) {
4493           DoSetChecked(false, true, true);
4494         }
4495       } else if (oldType == NS_FORM_INPUT_CHECKBOX) {
4496         bool originalIndeterminateValue =
4497           !!(aVisitor.mItemFlags & NS_ORIGINAL_INDETERMINATE_VALUE);
4498         SetIndeterminateInternal(originalIndeterminateValue, false);
4499         DoSetChecked(originalCheckedValue, true, true);
4500       }
4501     } else {
4502       // Fire input event and then change event.
4503       nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
4504                                            static_cast<nsIDOMHTMLInputElement*>(this),
4505                                            NS_LITERAL_STRING("input"), true,
4506                                            false);
4507 
4508       nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
4509                                            static_cast<nsIDOMHTMLInputElement*>(this),
4510                                            NS_LITERAL_STRING("change"), true,
4511                                            false);
4512 #ifdef ACCESSIBILITY
4513       // Fire an event to notify accessibility
4514       if (mType == NS_FORM_INPUT_CHECKBOX) {
4515         FireEventForAccessibility(this, aVisitor.mPresContext,
4516                                   NS_LITERAL_STRING("CheckboxStateChange"));
4517       } else {
4518         FireEventForAccessibility(this, aVisitor.mPresContext,
4519                                   NS_LITERAL_STRING("RadioStateChange"));
4520         // Fire event for the previous selected radio.
4521         nsCOMPtr<nsIDOMHTMLInputElement> previous =
4522           do_QueryInterface(aVisitor.mItemData);
4523         if (previous) {
4524           FireEventForAccessibility(previous, aVisitor.mPresContext,
4525                                     NS_LITERAL_STRING("RadioStateChange"));
4526         }
4527       }
4528 #endif
4529     }
4530   }
4531 
4532   if (NS_SUCCEEDED(rv)) {
4533     WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
4534     if (mType ==  NS_FORM_INPUT_NUMBER &&
4535         keyEvent && keyEvent->mMessage == eKeyPress &&
4536         aVisitor.mEvent->IsTrusted() &&
4537         (keyEvent->mKeyCode == NS_VK_UP || keyEvent->mKeyCode == NS_VK_DOWN) &&
4538         !IgnoreInputEventWithModifier(keyEvent)) {
4539       // We handle the up/down arrow keys specially for <input type=number>.
4540       // On some platforms the editor for the nested text control will
4541       // process these keys to send the cursor to the start/end of the text
4542       // control and as a result aVisitor.mEventStatus will already have been
4543       // set to nsEventStatus_eConsumeNoDefault. However, we know that
4544       // whenever the up/down arrow keys cause the value of the number
4545       // control to change the string in the text control will change, and
4546       // the cursor will be moved to the end of the text control, overwriting
4547       // the editor's handling of up/down keypress events. For that reason we
4548       // just ignore aVisitor.mEventStatus here and go ahead and handle the
4549       // event to increase/decrease the value of the number control.
4550       if (!aVisitor.mEvent->DefaultPreventedByContent() && IsMutable()) {
4551         StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
4552         FireChangeEventIfNeeded();
4553         aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4554       }
4555     } else if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
4556       switch (aVisitor.mEvent->mMessage) {
4557         case eFocus: {
4558           // see if we should select the contents of the textbox. This happens
4559           // for text and password fields when the field was focused by the
4560           // keyboard or a navigation, the platform allows it, and it wasn't
4561           // just because we raised a window.
4562           nsIFocusManager* fm = nsFocusManager::GetFocusManager();
4563           if (fm && IsSingleLineTextControl(false) &&
4564               !aVisitor.mEvent->AsFocusEvent()->mFromRaise &&
4565               SelectTextFieldOnFocus()) {
4566             nsIDocument* document = GetComposedDoc();
4567             if (document) {
4568               uint32_t lastFocusMethod;
4569               fm->GetLastFocusMethod(document->GetWindow(), &lastFocusMethod);
4570               if (lastFocusMethod &
4571                   (nsIFocusManager::FLAG_BYKEY | nsIFocusManager::FLAG_BYMOVEFOCUS)) {
4572                 RefPtr<nsPresContext> presContext =
4573                   GetPresContext(eForComposedDoc);
4574                 if (DispatchSelectEvent(presContext)) {
4575                   SelectAll(presContext);
4576                 }
4577               }
4578             }
4579           }
4580           break;
4581         }
4582 
4583         case eKeyPress:
4584         case eKeyUp:
4585         {
4586           // For backwards compat, trigger checks/radios/buttons with
4587           // space or enter (bug 25300)
4588           WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
4589           if ((aVisitor.mEvent->mMessage == eKeyPress &&
4590                keyEvent->mKeyCode == NS_VK_RETURN) ||
4591               (aVisitor.mEvent->mMessage == eKeyUp &&
4592                keyEvent->mKeyCode == NS_VK_SPACE)) {
4593             switch(mType) {
4594               case NS_FORM_INPUT_CHECKBOX:
4595               case NS_FORM_INPUT_RADIO:
4596               {
4597                 // Checkbox and Radio try to submit on Enter press
4598                 if (keyEvent->mKeyCode != NS_VK_SPACE) {
4599                   MaybeSubmitForm(aVisitor.mPresContext);
4600 
4601                   break;  // If we are submitting, do not send click event
4602                 }
4603                 // else fall through and treat Space like click...
4604                 MOZ_FALLTHROUGH;
4605               }
4606               case NS_FORM_INPUT_BUTTON:
4607               case NS_FORM_INPUT_RESET:
4608               case NS_FORM_INPUT_SUBMIT:
4609               case NS_FORM_INPUT_IMAGE: // Bug 34418
4610               case NS_FORM_INPUT_COLOR:
4611               {
4612                 DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(),
4613                                        aVisitor.mPresContext);
4614                 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4615               } // case
4616             } // switch
4617           }
4618           if (aVisitor.mEvent->mMessage == eKeyPress &&
4619               mType == NS_FORM_INPUT_RADIO && !keyEvent->IsAlt() &&
4620               !keyEvent->IsControl() && !keyEvent->IsMeta()) {
4621             bool isMovingBack = false;
4622             switch (keyEvent->mKeyCode) {
4623               case NS_VK_UP:
4624               case NS_VK_LEFT:
4625                 isMovingBack = true;
4626                 MOZ_FALLTHROUGH;
4627               case NS_VK_DOWN:
4628               case NS_VK_RIGHT:
4629               // Arrow key pressed, focus+select prev/next radio button
4630               nsIRadioGroupContainer* container = GetRadioGroupContainer();
4631               if (container) {
4632                 nsAutoString name;
4633                 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
4634                 RefPtr<HTMLInputElement> selectedRadioButton;
4635                 container->GetNextRadioButton(name, isMovingBack, this,
4636                                               getter_AddRefs(selectedRadioButton));
4637                 if (selectedRadioButton) {
4638                   rv = selectedRadioButton->Focus();
4639                   if (NS_SUCCEEDED(rv)) {
4640                     rv = DispatchSimulatedClick(selectedRadioButton,
4641                                                 aVisitor.mEvent->IsTrusted(),
4642                                                 aVisitor.mPresContext);
4643                     if (NS_SUCCEEDED(rv)) {
4644                       aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4645                     }
4646                   }
4647                 }
4648               }
4649             }
4650           }
4651 
4652           /*
4653            * For some input types, if the user hits enter, the form is submitted.
4654            *
4655            * Bug 99920, bug 109463 and bug 147850:
4656            * (a) if there is a submit control in the form, click the first
4657            *     submit control in the form.
4658            * (b) if there is just one text control in the form, submit by
4659            *     sending a submit event directly to the form
4660            * (c) if there is more than one text input and no submit buttons, do
4661            *     not submit, period.
4662            */
4663 
4664           if (aVisitor.mEvent->mMessage == eKeyPress &&
4665               keyEvent->mKeyCode == NS_VK_RETURN &&
4666                (IsSingleLineTextControl(false, mType) ||
4667                 mType == NS_FORM_INPUT_NUMBER ||
4668                 IsExperimentalMobileType(mType) ||
4669                 IsDateTimeInputType(mType))) {
4670             FireChangeEventIfNeeded();
4671             rv = MaybeSubmitForm(aVisitor.mPresContext);
4672             NS_ENSURE_SUCCESS(rv, rv);
4673           }
4674 
4675           if (aVisitor.mEvent->mMessage == eKeyPress &&
4676               mType == NS_FORM_INPUT_RANGE && !keyEvent->IsAlt() &&
4677               !keyEvent->IsControl() && !keyEvent->IsMeta() &&
4678               (keyEvent->mKeyCode == NS_VK_LEFT ||
4679                keyEvent->mKeyCode == NS_VK_RIGHT ||
4680                keyEvent->mKeyCode == NS_VK_UP ||
4681                keyEvent->mKeyCode == NS_VK_DOWN ||
4682                keyEvent->mKeyCode == NS_VK_PAGE_UP ||
4683                keyEvent->mKeyCode == NS_VK_PAGE_DOWN ||
4684                keyEvent->mKeyCode == NS_VK_HOME ||
4685                keyEvent->mKeyCode == NS_VK_END)) {
4686             Decimal minimum = GetMinimum();
4687             Decimal maximum = GetMaximum();
4688             MOZ_ASSERT(minimum.isFinite() && maximum.isFinite());
4689             if (minimum < maximum) { // else the value is locked to the minimum
4690               Decimal value = GetValueAsDecimal();
4691               Decimal step = GetStep();
4692               if (step == kStepAny) {
4693                 step = GetDefaultStep();
4694               }
4695               MOZ_ASSERT(value.isFinite() && step.isFinite());
4696               Decimal newValue;
4697               switch (keyEvent->mKeyCode) {
4698                 case  NS_VK_LEFT:
4699                   newValue = value + (GetComputedDirectionality() == eDir_RTL
4700                                         ? step : -step);
4701                   break;
4702                 case  NS_VK_RIGHT:
4703                   newValue = value + (GetComputedDirectionality() == eDir_RTL
4704                                         ? -step : step);
4705                   break;
4706                 case  NS_VK_UP:
4707                   // Even for horizontal range, "up" means "increase"
4708                   newValue = value + step;
4709                   break;
4710                 case  NS_VK_DOWN:
4711                   // Even for horizontal range, "down" means "decrease"
4712                   newValue = value - step;
4713                   break;
4714                 case  NS_VK_HOME:
4715                   newValue = minimum;
4716                   break;
4717                 case  NS_VK_END:
4718                   newValue = maximum;
4719                   break;
4720                 case  NS_VK_PAGE_UP:
4721                   // For PgUp/PgDn we jump 10% of the total range, unless step
4722                   // requires us to jump more.
4723                   newValue = value + std::max(step, (maximum - minimum) / Decimal(10));
4724                   break;
4725                 case  NS_VK_PAGE_DOWN:
4726                   newValue = value - std::max(step, (maximum - minimum) / Decimal(10));
4727                   break;
4728               }
4729               SetValueOfRangeForUserEvent(newValue);
4730               FireChangeEventIfNeeded();
4731               aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4732             }
4733           }
4734 
4735         } break; // eKeyPress || eKeyUp
4736 
4737         case eMouseDown:
4738         case eMouseUp:
4739         case eMouseDoubleClick: {
4740           // cancel all of these events for buttons
4741           //XXXsmaug Why?
4742           WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
4743           if (mouseEvent->button == WidgetMouseEvent::eMiddleButton ||
4744               mouseEvent->button == WidgetMouseEvent::eRightButton) {
4745             if (mType == NS_FORM_INPUT_BUTTON ||
4746                 mType == NS_FORM_INPUT_RESET ||
4747                 mType == NS_FORM_INPUT_SUBMIT) {
4748               if (aVisitor.mDOMEvent) {
4749                 aVisitor.mDOMEvent->StopPropagation();
4750               } else {
4751                 rv = NS_ERROR_FAILURE;
4752               }
4753             }
4754           }
4755           if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
4756             if (mouseEvent->button == WidgetMouseEvent::eLeftButton &&
4757                 !IgnoreInputEventWithModifier(mouseEvent)) {
4758               nsNumberControlFrame* numberControlFrame =
4759                 do_QueryFrame(GetPrimaryFrame());
4760               if (numberControlFrame) {
4761                 if (aVisitor.mEvent->mMessage == eMouseDown &&
4762                     IsMutable()) {
4763                   switch (numberControlFrame->GetSpinButtonForPointerEvent(
4764                             aVisitor.mEvent->AsMouseEvent())) {
4765                   case nsNumberControlFrame::eSpinButtonUp:
4766                     StepNumberControlForUserEvent(1);
4767                     mNumberControlSpinnerSpinsUp = true;
4768                     StartNumberControlSpinnerSpin();
4769                     aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4770                     break;
4771                   case nsNumberControlFrame::eSpinButtonDown:
4772                     StepNumberControlForUserEvent(-1);
4773                     mNumberControlSpinnerSpinsUp = false;
4774                     StartNumberControlSpinnerSpin();
4775                     aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4776                     break;
4777                   }
4778                 }
4779               }
4780             }
4781             if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
4782               // We didn't handle this to step up/down. Whatever this was, be
4783               // aggressive about stopping the spin. (And don't set
4784               // nsEventStatus_eConsumeNoDefault after doing so, since that
4785               // might prevent, say, the context menu from opening.)
4786               StopNumberControlSpinnerSpin();
4787             }
4788           }
4789           break;
4790         }
4791 #if !defined(ANDROID) && !defined(XP_MACOSX)
4792         case eWheel: {
4793           // Handle wheel events as increasing / decreasing the input element's
4794           // value when it's focused and it's type is number or range.
4795           WidgetWheelEvent* wheelEvent = aVisitor.mEvent->AsWheelEvent();
4796           if (!aVisitor.mEvent->DefaultPrevented() &&
4797               aVisitor.mEvent->IsTrusted() && IsMutable() && wheelEvent &&
4798               wheelEvent->mDeltaY != 0 &&
4799               wheelEvent->mDeltaMode != nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
4800             if (mType == NS_FORM_INPUT_NUMBER) {
4801               nsNumberControlFrame* numberControlFrame =
4802                 do_QueryFrame(GetPrimaryFrame());
4803               if (numberControlFrame && numberControlFrame->IsFocused()) {
4804                 StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
4805                 FireChangeEventIfNeeded();
4806                 aVisitor.mEvent->PreventDefault();
4807               }
4808             } else if (mType == NS_FORM_INPUT_RANGE &&
4809                        nsContentUtils::IsFocusedContent(this) &&
4810                        GetMinimum() < GetMaximum()) {
4811               Decimal value = GetValueAsDecimal();
4812               Decimal step = GetStep();
4813               if (step == kStepAny) {
4814                 step = GetDefaultStep();
4815               }
4816               MOZ_ASSERT(value.isFinite() && step.isFinite());
4817               SetValueOfRangeForUserEvent(wheelEvent->mDeltaY < 0 ?
4818                                           value + step : value - step);
4819               FireChangeEventIfNeeded();
4820               aVisitor.mEvent->PreventDefault();
4821             }
4822           }
4823           break;
4824         }
4825 #endif
4826         default:
4827           break;
4828       }
4829 
4830       if (outerActivateEvent) {
4831         if (mForm && (oldType == NS_FORM_INPUT_SUBMIT ||
4832                       oldType == NS_FORM_INPUT_IMAGE)) {
4833           if (mType != NS_FORM_INPUT_SUBMIT && mType != NS_FORM_INPUT_IMAGE) {
4834             // If the type has changed to a non-submit type, then we want to
4835             // flush the stored submission if there is one (as if the submit()
4836             // was allowed to succeed)
4837             mForm->FlushPendingSubmission();
4838           }
4839         }
4840         switch(mType) {
4841         case NS_FORM_INPUT_RESET:
4842         case NS_FORM_INPUT_SUBMIT:
4843         case NS_FORM_INPUT_IMAGE:
4844           if (mForm) {
4845             InternalFormEvent event(true,
4846               (mType == NS_FORM_INPUT_RESET) ? eFormReset : eFormSubmit);
4847             event.mOriginator = this;
4848             nsEventStatus status  = nsEventStatus_eIgnore;
4849 
4850             nsCOMPtr<nsIPresShell> presShell =
4851               aVisitor.mPresContext->GetPresShell();
4852 
4853             // If |nsIPresShell::Destroy| has been called due to
4854             // handling the event the pres context will return a null
4855             // pres shell.  See bug 125624.
4856             // TODO: removing this code and have the submit event sent by the
4857             // form, see bug 592124.
4858             if (presShell && (event.mMessage != eFormSubmit ||
4859                               mForm->SubmissionCanProceed(this))) {
4860               // Hold a strong ref while dispatching
4861               RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
4862               presShell->HandleDOMEventWithTarget(form, &event, &status);
4863               aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4864             }
4865           }
4866           break;
4867 
4868         default:
4869           break;
4870         } //switch
4871       } //click or outer activate event
4872     } else if (outerActivateEvent &&
4873                (oldType == NS_FORM_INPUT_SUBMIT ||
4874                 oldType == NS_FORM_INPUT_IMAGE) &&
4875                mForm) {
4876       // tell the form to flush a possible pending submission.
4877       // the reason is that the script returned false (the event was
4878       // not ignored) so if there is a stored submission, it needs to
4879       // be submitted immediately.
4880       mForm->FlushPendingSubmission();
4881     }
4882   } // if
4883 
4884   if (NS_SUCCEEDED(rv) && mType == NS_FORM_INPUT_RANGE) {
4885     PostHandleEventForRangeThumb(aVisitor);
4886   }
4887 
4888   return MaybeInitPickers(aVisitor);
4889 }
4890 
4891 void
PostHandleEventForRangeThumb(EventChainPostVisitor & aVisitor)4892 HTMLInputElement::PostHandleEventForRangeThumb(EventChainPostVisitor& aVisitor)
4893 {
4894   MOZ_ASSERT(mType == NS_FORM_INPUT_RANGE);
4895 
4896   if (nsEventStatus_eConsumeNoDefault == aVisitor.mEventStatus ||
4897       !(aVisitor.mEvent->mClass == eMouseEventClass ||
4898         aVisitor.mEvent->mClass == eTouchEventClass ||
4899         aVisitor.mEvent->mClass == eKeyboardEventClass)) {
4900     return;
4901   }
4902 
4903   nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4904   if (!rangeFrame && mIsDraggingRange) {
4905     CancelRangeThumbDrag();
4906     return;
4907   }
4908 
4909   switch (aVisitor.mEvent->mMessage)
4910   {
4911     case eMouseDown:
4912     case eTouchStart: {
4913       if (mIsDraggingRange) {
4914         break;
4915       }
4916       if (nsIPresShell::GetCapturingContent()) {
4917         break; // don't start drag if someone else is already capturing
4918       }
4919       WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
4920       if (IgnoreInputEventWithModifier(inputEvent)) {
4921         break; // ignore
4922       }
4923       if (aVisitor.mEvent->mMessage == eMouseDown) {
4924         if (aVisitor.mEvent->AsMouseEvent()->buttons ==
4925               WidgetMouseEvent::eLeftButtonFlag) {
4926           StartRangeThumbDrag(inputEvent);
4927         } else if (mIsDraggingRange) {
4928           CancelRangeThumbDrag();
4929         }
4930       } else {
4931         if (aVisitor.mEvent->AsTouchEvent()->mTouches.Length() == 1) {
4932           StartRangeThumbDrag(inputEvent);
4933         } else if (mIsDraggingRange) {
4934           CancelRangeThumbDrag();
4935         }
4936       }
4937       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4938     } break;
4939 
4940     case eMouseMove:
4941     case eTouchMove:
4942       if (!mIsDraggingRange) {
4943         break;
4944       }
4945       if (nsIPresShell::GetCapturingContent() != this) {
4946         // Someone else grabbed capture.
4947         CancelRangeThumbDrag();
4948         break;
4949       }
4950       SetValueOfRangeForUserEvent(
4951         rangeFrame->GetValueAtEventPoint(aVisitor.mEvent->AsInputEvent()));
4952       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4953       break;
4954 
4955     case eMouseUp:
4956     case eTouchEnd:
4957       if (!mIsDraggingRange) {
4958         break;
4959       }
4960       // We don't check to see whether we are the capturing content here and
4961       // call CancelRangeThumbDrag() if that is the case. We just finish off
4962       // the drag and set our final value (unless someone has called
4963       // preventDefault() and prevents us getting here).
4964       FinishRangeThumbDrag(aVisitor.mEvent->AsInputEvent());
4965       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4966       break;
4967 
4968     case eKeyPress:
4969       if (mIsDraggingRange &&
4970           aVisitor.mEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
4971         CancelRangeThumbDrag();
4972       }
4973       break;
4974 
4975     case eTouchCancel:
4976       if (mIsDraggingRange) {
4977         CancelRangeThumbDrag();
4978       }
4979       break;
4980 
4981     default:
4982       break;
4983   }
4984 }
4985 
4986 void
MaybeLoadImage()4987 HTMLInputElement::MaybeLoadImage()
4988 {
4989   // Our base URI may have changed; claim that our URI changed, and the
4990   // nsImageLoadingContent will decide whether a new image load is warranted.
4991   nsAutoString uri;
4992   if (mType == NS_FORM_INPUT_IMAGE &&
4993       GetAttr(kNameSpaceID_None, nsGkAtoms::src, uri) &&
4994       (NS_FAILED(LoadImage(uri, false, true, eImageLoadType_Normal)) ||
4995        !LoadingEnabled())) {
4996     CancelImageRequests(true);
4997   }
4998 }
4999 
5000 nsresult
BindToTree(nsIDocument * aDocument,nsIContent * aParent,nsIContent * aBindingParent,bool aCompileEventHandlers)5001 HTMLInputElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
5002                              nsIContent* aBindingParent,
5003                              bool aCompileEventHandlers)
5004 {
5005   nsresult rv = nsGenericHTMLFormElementWithState::BindToTree(aDocument, aParent,
5006                                                               aBindingParent,
5007                                                               aCompileEventHandlers);
5008   NS_ENSURE_SUCCESS(rv, rv);
5009 
5010   nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent,
5011                                     aCompileEventHandlers);
5012 
5013   if (mType == NS_FORM_INPUT_IMAGE) {
5014     // Our base URI may have changed; claim that our URI changed, and the
5015     // nsImageLoadingContent will decide whether a new image load is warranted.
5016     if (HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
5017       // FIXME: Bug 660963 it would be nice if we could just have
5018       // ClearBrokenState update our state and do it fast...
5019       ClearBrokenState();
5020       RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
5021       nsContentUtils::AddScriptRunner(
5022         NewRunnableMethod(this, &HTMLInputElement::MaybeLoadImage));
5023     }
5024   }
5025 
5026   // Add radio to document if we don't have a form already (if we do it's
5027   // already been added into that group)
5028   if (aDocument && !mForm && mType == NS_FORM_INPUT_RADIO) {
5029     AddedToRadioGroup();
5030   }
5031 
5032   // Set direction based on value if dir=auto
5033   SetDirectionIfAuto(HasDirAuto(), false);
5034 
5035   // An element can't suffer from value missing if it is not in a document.
5036   // We have to check if we suffer from that as we are now in a document.
5037   UpdateValueMissingValidityState();
5038 
5039   // If there is a disabled fieldset in the parent chain, the element is now
5040   // barred from constraint validation and can't suffer from value missing
5041   // (call done before).
5042   UpdateBarredFromConstraintValidation();
5043 
5044   // And now make sure our state is up to date
5045   UpdateState(false);
5046 
5047   if (mType == NS_FORM_INPUT_PASSWORD) {
5048     if (IsInComposedDoc()) {
5049       AsyncEventDispatcher* dispatcher =
5050         new AsyncEventDispatcher(this,
5051                                  NS_LITERAL_STRING("DOMInputPasswordAdded"),
5052                                  true,
5053                                  true);
5054       dispatcher->PostDOMEvent();
5055     }
5056 
5057 #ifdef EARLY_BETA_OR_EARLIER
5058     Telemetry::Accumulate(Telemetry::PWMGR_PASSWORD_INPUT_IN_FORM, !!mForm);
5059 #endif
5060   }
5061 
5062   return rv;
5063 }
5064 
5065 void
UnbindFromTree(bool aDeep,bool aNullParent)5066 HTMLInputElement::UnbindFromTree(bool aDeep, bool aNullParent)
5067 {
5068   // If we have a form and are unbound from it,
5069   // nsGenericHTMLFormElementWithState::UnbindFromTree() will unset the form and
5070   // that takes care of form's WillRemove so we just have to take care
5071   // of the case where we're removing from the document and we don't
5072   // have a form
5073   if (!mForm && mType == NS_FORM_INPUT_RADIO) {
5074     WillRemoveFromRadioGroup();
5075   }
5076 
5077   nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
5078   nsGenericHTMLFormElementWithState::UnbindFromTree(aDeep, aNullParent);
5079 
5080   // GetCurrentDoc is returning nullptr so we can update the value
5081   // missing validity state to reflect we are no longer into a doc.
5082   UpdateValueMissingValidityState();
5083   // We might be no longer disabled because of parent chain changed.
5084   UpdateBarredFromConstraintValidation();
5085 
5086   // And now make sure our state is up to date
5087   UpdateState(false);
5088 }
5089 
5090 void
HandleTypeChange(uint8_t aNewType)5091 HTMLInputElement::HandleTypeChange(uint8_t aNewType)
5092 {
5093   if (mType == NS_FORM_INPUT_RANGE && mIsDraggingRange) {
5094     CancelRangeThumbDrag(false);
5095   }
5096 
5097   ValueModeType aOldValueMode = GetValueMode();
5098   uint8_t oldType = mType;
5099   nsAutoString aOldValue;
5100 
5101   if (aOldValueMode == VALUE_MODE_VALUE) {
5102     GetValue(aOldValue);
5103   }
5104 
5105   nsTextEditorState::SelectionProperties sp;
5106 
5107   if (GetEditorState()) {
5108     sp = mInputData.mState->GetSelectionProperties();
5109   }
5110 
5111   // We already have a copy of the value, lets free it and changes the type.
5112   FreeData();
5113   mType = aNewType;
5114 
5115   if (IsSingleLineTextControl()) {
5116 
5117     mInputData.mState = new nsTextEditorState(this);
5118     if (!sp.IsDefault()) {
5119       mInputData.mState->SetSelectionProperties(sp);
5120     }
5121   }
5122 
5123   /**
5124    * The following code is trying to reproduce the algorithm described here:
5125    * http://www.whatwg.org/specs/web-apps/current-work/complete.html#input-type-change
5126    */
5127   switch (GetValueMode()) {
5128     case VALUE_MODE_DEFAULT:
5129     case VALUE_MODE_DEFAULT_ON:
5130       // If the previous value mode was value, we need to set the value content
5131       // attribute to the previous value.
5132       // There is no value sanitizing algorithm for elements in this mode.
5133       if (aOldValueMode == VALUE_MODE_VALUE && !aOldValue.IsEmpty()) {
5134         SetAttr(kNameSpaceID_None, nsGkAtoms::value, aOldValue, true);
5135       }
5136       break;
5137     case VALUE_MODE_VALUE:
5138       // If the previous value mode wasn't value, we have to set the value to
5139       // the value content attribute.
5140       // SetValueInternal is going to sanitize the value.
5141       {
5142         nsAutoString value;
5143         if (aOldValueMode != VALUE_MODE_VALUE) {
5144           GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
5145         } else {
5146           value = aOldValue;
5147         }
5148         // TODO: What should we do if SetValueInternal fails?  (The allocation
5149         // may potentially be big, but most likely we've failed to allocate
5150         // before the type change.)
5151         SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
5152       }
5153       break;
5154     case VALUE_MODE_FILENAME:
5155     default:
5156       // We don't care about the value.
5157       // There is no value sanitizing algorithm for elements in this mode.
5158       break;
5159   }
5160 
5161   // Updating mFocusedValue in consequence:
5162   // If the new type fires a change event on blur, but the previous type
5163   // doesn't, we should set mFocusedValue to the current value.
5164   // Otherwise, if the new type doesn't fire a change event on blur, but the
5165   // previous type does, we should clear out mFocusedValue.
5166   if (MayFireChangeOnBlur(mType) && !MayFireChangeOnBlur(oldType)) {
5167     GetValue(mFocusedValue);
5168   } else if (!IsSingleLineTextControl(false, mType) &&
5169              IsSingleLineTextControl(false, oldType)) {
5170     mFocusedValue.Truncate();
5171   }
5172 
5173   UpdateHasRange();
5174 
5175   // Do not notify, it will be done after if needed.
5176   UpdateAllValidityStates(false);
5177 
5178   UpdateApzAwareFlag();
5179 }
5180 
5181 void
SanitizeValue(nsAString & aValue)5182 HTMLInputElement::SanitizeValue(nsAString& aValue)
5183 {
5184   NS_ASSERTION(mDoneCreating, "The element creation should be finished!");
5185 
5186   switch (mType) {
5187     case NS_FORM_INPUT_TEXT:
5188     case NS_FORM_INPUT_SEARCH:
5189     case NS_FORM_INPUT_TEL:
5190     case NS_FORM_INPUT_PASSWORD:
5191       {
5192         char16_t crlf[] = { char16_t('\r'), char16_t('\n'), 0 };
5193         aValue.StripChars(crlf);
5194       }
5195       break;
5196     case NS_FORM_INPUT_EMAIL:
5197     case NS_FORM_INPUT_URL:
5198       {
5199         char16_t crlf[] = { char16_t('\r'), char16_t('\n'), 0 };
5200         aValue.StripChars(crlf);
5201 
5202         aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(aValue);
5203       }
5204       break;
5205     case NS_FORM_INPUT_NUMBER:
5206       {
5207         Decimal value;
5208         bool ok = ConvertStringToNumber(aValue, value);
5209         if (!ok) {
5210           aValue.Truncate();
5211         }
5212       }
5213       break;
5214     case NS_FORM_INPUT_RANGE:
5215       {
5216         Decimal minimum = GetMinimum();
5217         Decimal maximum = GetMaximum();
5218         MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
5219                    "type=range should have a default maximum/minimum");
5220 
5221         // We use this to avoid modifying the string unnecessarily, since that
5222         // may introduce rounding. This is set to true only if the value we
5223         // parse out from aValue needs to be sanitized.
5224         bool needSanitization = false;
5225 
5226         Decimal value;
5227         bool ok = ConvertStringToNumber(aValue, value);
5228         if (!ok) {
5229           needSanitization = true;
5230           // Set value to midway between minimum and maximum.
5231           value = maximum <= minimum ? minimum : minimum + (maximum - minimum)/Decimal(2);
5232         } else if (value < minimum || maximum < minimum) {
5233           needSanitization = true;
5234           value = minimum;
5235         } else if (value > maximum) {
5236           needSanitization = true;
5237           value = maximum;
5238         }
5239 
5240         Decimal step = GetStep();
5241         if (step != kStepAny) {
5242           Decimal stepBase = GetStepBase();
5243           // There could be rounding issues below when dealing with fractional
5244           // numbers, but let's ignore that until ECMAScript supplies us with a
5245           // decimal number type.
5246           Decimal deltaToStep = NS_floorModulo(value - stepBase, step);
5247           if (deltaToStep != Decimal(0)) {
5248             // "suffering from a step mismatch"
5249             // Round the element's value to the nearest number for which the
5250             // element would not suffer from a step mismatch, and which is
5251             // greater than or equal to the minimum, and, if the maximum is not
5252             // less than the minimum, which is less than or equal to the
5253             // maximum, if there is a number that matches these constraints:
5254             MOZ_ASSERT(deltaToStep > Decimal(0), "stepBelow/stepAbove will be wrong");
5255             Decimal stepBelow = value - deltaToStep;
5256             Decimal stepAbove = value - deltaToStep + step;
5257             Decimal halfStep = step / Decimal(2);
5258             bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
5259             bool stepAboveInRange = stepAbove >= minimum &&
5260                                     stepAbove <= maximum;
5261             bool stepBelowInRange = stepBelow >= minimum &&
5262                                     stepBelow <= maximum;
5263 
5264             if ((stepAboveIsClosest || !stepBelowInRange) && stepAboveInRange) {
5265               needSanitization = true;
5266               value = stepAbove;
5267             } else if ((!stepAboveIsClosest || !stepAboveInRange) && stepBelowInRange) {
5268               needSanitization = true;
5269               value = stepBelow;
5270             }
5271           }
5272         }
5273 
5274         if (needSanitization) {
5275           char buf[32];
5276           DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
5277           aValue.AssignASCII(buf);
5278           MOZ_ASSERT(ok, "buf not big enough");
5279         }
5280       }
5281       break;
5282     case NS_FORM_INPUT_DATE:
5283       {
5284         if (!aValue.IsEmpty() && !IsValidDate(aValue)) {
5285           aValue.Truncate();
5286         }
5287       }
5288       break;
5289     case NS_FORM_INPUT_TIME:
5290       {
5291         if (!aValue.IsEmpty() && !IsValidTime(aValue)) {
5292           aValue.Truncate();
5293         }
5294       }
5295       break;
5296     case NS_FORM_INPUT_MONTH:
5297       {
5298         if (!aValue.IsEmpty() && !IsValidMonth(aValue)) {
5299           aValue.Truncate();
5300         }
5301       }
5302       break;
5303     case NS_FORM_INPUT_WEEK:
5304       {
5305         if (!aValue.IsEmpty() && !IsValidWeek(aValue)) {
5306           aValue.Truncate();
5307         }
5308       }
5309       break;
5310     case NS_FORM_INPUT_DATETIME_LOCAL:
5311       {
5312         if (!aValue.IsEmpty() && !IsValidDateTimeLocal(aValue)) {
5313           aValue.Truncate();
5314         } else {
5315           NormalizeDateTimeLocal(aValue);
5316         }
5317       }
5318       break;
5319     case NS_FORM_INPUT_COLOR:
5320       {
5321         if (IsValidSimpleColor(aValue)) {
5322           ToLowerCase(aValue);
5323         } else {
5324           // Set default (black) color, if aValue wasn't parsed correctly.
5325           aValue.AssignLiteral("#000000");
5326         }
5327       }
5328       break;
5329   }
5330 }
5331 
IsValidSimpleColor(const nsAString & aValue) const5332 bool HTMLInputElement::IsValidSimpleColor(const nsAString& aValue) const
5333 {
5334   if (aValue.Length() != 7 || aValue.First() != '#') {
5335     return false;
5336   }
5337 
5338   for (int i = 1; i < 7; ++i) {
5339     if (!nsCRT::IsAsciiDigit(aValue[i]) &&
5340         !(aValue[i] >= 'a' && aValue[i] <= 'f') &&
5341         !(aValue[i] >= 'A' && aValue[i] <= 'F')) {
5342       return false;
5343     }
5344   }
5345   return true;
5346 }
5347 
5348 bool
IsLeapYear(uint32_t aYear) const5349 HTMLInputElement::IsLeapYear(uint32_t aYear) const
5350 {
5351   if ((aYear % 4 == 0 && aYear % 100 != 0) || ( aYear % 400 == 0)) {
5352     return true;
5353   }
5354   return false;
5355 }
5356 
5357 uint32_t
DayOfWeek(uint32_t aYear,uint32_t aMonth,uint32_t aDay,bool isoWeek) const5358 HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay,
5359                             bool isoWeek) const
5360 {
5361   // Tomohiko Sakamoto algorithm.
5362   int monthTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
5363   aYear -= aMonth < 3;
5364 
5365   uint32_t day = (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
5366                   monthTable[aMonth - 1] + aDay) % 7;
5367 
5368   if (isoWeek) {
5369     return ((day + 6) % 7) + 1;
5370   }
5371 
5372   return day;
5373 }
5374 
5375 uint32_t
MaximumWeekInYear(uint32_t aYear) const5376 HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const
5377 {
5378   int day = DayOfWeek(aYear, 1, 1, true); // January 1.
5379   // A year starting on Thursday or a leap year starting on Wednesday has 53
5380   // weeks. All other years have 52 weeks.
5381   return day == 4 || (day == 3 && IsLeapYear(aYear)) ?
5382     kMaximumWeekInYear : kMaximumWeekInYear - 1;
5383 }
5384 
5385 bool
IsValidWeek(const nsAString & aValue) const5386 HTMLInputElement::IsValidWeek(const nsAString& aValue) const
5387 {
5388   uint32_t year, week;
5389   return ParseWeek(aValue, &year, &week);
5390 }
5391 
5392 bool
IsValidMonth(const nsAString & aValue) const5393 HTMLInputElement::IsValidMonth(const nsAString& aValue) const
5394 {
5395   uint32_t year, month;
5396   return ParseMonth(aValue, &year, &month);
5397 }
5398 
5399 bool
IsValidDate(const nsAString & aValue) const5400 HTMLInputElement::IsValidDate(const nsAString& aValue) const
5401 {
5402   uint32_t year, month, day;
5403   return ParseDate(aValue, &year, &month, &day);
5404 }
5405 
5406 bool
IsValidDateTimeLocal(const nsAString & aValue) const5407 HTMLInputElement::IsValidDateTimeLocal(const nsAString& aValue) const
5408 {
5409   uint32_t year, month, day, time;
5410   return ParseDateTimeLocal(aValue, &year, &month, &day, &time);
5411 }
5412 
5413 bool
ParseYear(const nsAString & aValue,uint32_t * aYear) const5414 HTMLInputElement::ParseYear(const nsAString& aValue, uint32_t* aYear) const
5415 {
5416   if (aValue.Length() < 4) {
5417     return false;
5418   }
5419 
5420   return DigitSubStringToNumber(aValue, 0, aValue.Length(), aYear) &&
5421       *aYear > 0;
5422 }
5423 
5424 bool
ParseMonth(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth) const5425 HTMLInputElement::ParseMonth(const nsAString& aValue, uint32_t* aYear,
5426                              uint32_t* aMonth) const
5427 {
5428   // Parse the year, month values out a string formatted as 'yyyy-mm'.
5429   if (aValue.Length() < 7) {
5430     return false;
5431   }
5432 
5433   uint32_t endOfYearOffset = aValue.Length() - 3;
5434   if (aValue[endOfYearOffset] != '-') {
5435     return false;
5436   }
5437 
5438   const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
5439   if (!ParseYear(yearStr, aYear)) {
5440     return false;
5441   }
5442 
5443   return DigitSubStringToNumber(aValue, endOfYearOffset + 1, 2, aMonth) &&
5444          *aMonth > 0 && *aMonth <= 12;
5445 }
5446 
5447 bool
ParseWeek(const nsAString & aValue,uint32_t * aYear,uint32_t * aWeek) const5448 HTMLInputElement::ParseWeek(const nsAString& aValue, uint32_t* aYear,
5449                             uint32_t* aWeek) const
5450 {
5451   // Parse the year, month values out a string formatted as 'yyyy-Www'.
5452   if (aValue.Length() < 8) {
5453     return false;
5454   }
5455 
5456   uint32_t endOfYearOffset = aValue.Length() - 4;
5457   if (aValue[endOfYearOffset] != '-') {
5458     return false;
5459   }
5460 
5461   if (aValue[endOfYearOffset + 1] != 'W') {
5462     return false;
5463   }
5464 
5465   const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
5466   if (!ParseYear(yearStr, aYear)) {
5467     return false;
5468   }
5469 
5470   return DigitSubStringToNumber(aValue, endOfYearOffset + 2, 2, aWeek) &&
5471          *aWeek > 0 && *aWeek <= MaximumWeekInYear(*aYear);
5472 
5473 }
5474 
5475 bool
ParseDate(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth,uint32_t * aDay) const5476 HTMLInputElement::ParseDate(const nsAString& aValue, uint32_t* aYear,
5477                             uint32_t* aMonth, uint32_t* aDay) const
5478 {
5479 /*
5480  * Parse the year, month, day values out a date string formatted as 'yyyy-mm-dd'.
5481  * -The year must be 4 or more digits long, and year > 0
5482  * -The month must be exactly 2 digits long, and 01 <= month <= 12
5483  * -The day must be exactly 2 digit long, and 01 <= day <= maxday
5484  *  Where maxday is the number of days in the month 'month' and year 'year'
5485  */
5486   if (aValue.Length() < 10) {
5487     return false;
5488   }
5489 
5490   uint32_t endOfMonthOffset = aValue.Length() - 3;
5491   if (aValue[endOfMonthOffset] != '-') {
5492     return false;
5493   }
5494 
5495   const nsAString& yearMonthStr = Substring(aValue, 0, endOfMonthOffset);
5496   if (!ParseMonth(yearMonthStr, aYear, aMonth)) {
5497     return false;
5498   }
5499 
5500   return DigitSubStringToNumber(aValue, endOfMonthOffset + 1, 2, aDay) &&
5501          *aDay > 0 && *aDay <= NumberOfDaysInMonth(*aMonth, *aYear);
5502 }
5503 
5504 bool
ParseDateTimeLocal(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth,uint32_t * aDay,uint32_t * aTime) const5505 HTMLInputElement::ParseDateTimeLocal(const nsAString& aValue, uint32_t* aYear,
5506                                      uint32_t* aMonth, uint32_t* aDay,
5507                                      uint32_t* aTime) const
5508 {
5509   // Parse the year, month, day and time values out a string formatted as
5510   // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
5511   // seconds can be 1 to 3 digits.
5512   // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
5513   // or 'yyyy-mm-dd hh:mm'.
5514   if (aValue.Length() < 16) {
5515     return false;
5516   }
5517 
5518   const uint32_t sepIndex = 10;
5519   if (aValue[sepIndex] != 'T' && aValue[sepIndex] != ' ') {
5520     return false;
5521   }
5522 
5523   const nsAString& dateStr = Substring(aValue, 0, sepIndex);
5524   if (!ParseDate(dateStr, aYear, aMonth, aDay)) {
5525     return false;
5526   }
5527 
5528   const nsAString& timeStr = Substring(aValue, sepIndex + 1,
5529                                        aValue.Length() - sepIndex + 1);
5530   if (!ParseTime(timeStr, aTime)) {
5531     return false;
5532   }
5533 
5534   return true;
5535 }
5536 
5537 void
NormalizeDateTimeLocal(nsAString & aValue) const5538 HTMLInputElement::NormalizeDateTimeLocal(nsAString& aValue) const
5539 {
5540   if (aValue.IsEmpty()) {
5541     return;
5542   }
5543 
5544   // Use 'T' as the separator between date string and time string.
5545   const uint32_t sepIndex = 10;
5546   if (aValue[sepIndex] == ' ') {
5547     aValue.Replace(sepIndex, 1, NS_LITERAL_STRING("T"));
5548   }
5549 
5550   // Time expressed as the shortest possible string.
5551   if (aValue.Length() == 16) {
5552     return;
5553   }
5554 
5555   // Fractions of seconds part is optional, ommit it if it's 0.
5556   if (aValue.Length() > 19) {
5557     uint32_t milliseconds;
5558     if (!DigitSubStringToNumber(aValue, 20, aValue.Length() - 20,
5559                                 &milliseconds)) {
5560       return;
5561     }
5562 
5563     if (milliseconds != 0) {
5564       return;
5565     }
5566 
5567     aValue.Cut(19, aValue.Length() - 19);
5568   }
5569 
5570   // Seconds part is optional, ommit it if it's 0.
5571   uint32_t seconds;
5572   if (!DigitSubStringToNumber(aValue, 17, aValue.Length() - 17, &seconds)) {
5573     return;
5574   }
5575 
5576   if (seconds != 0) {
5577     return;
5578   }
5579 
5580   aValue.Cut(16, aValue.Length() - 16);
5581 }
5582 
5583 double
DaysSinceEpochFromWeek(uint32_t aYear,uint32_t aWeek) const5584 HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear, uint32_t aWeek) const
5585 {
5586   double days = JS::DayFromYear(aYear) + (aWeek - 1) * 7;
5587   uint32_t dayOneIsoWeekday = DayOfWeek(aYear, 1, 1, true);
5588 
5589   // If day one of that year is on/before Thursday, we should subtract the
5590   // days that belong to last year in our first week, otherwise, our first
5591   // days belong to last year's last week, and we should add those days
5592   // back.
5593   if (dayOneIsoWeekday <= 4) {
5594     days -= (dayOneIsoWeekday - 1);
5595   } else {
5596     days += (7 - dayOneIsoWeekday + 1);
5597   }
5598 
5599   return days;
5600 }
5601 
5602 uint32_t
NumberOfDaysInMonth(uint32_t aMonth,uint32_t aYear) const5603 HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const
5604 {
5605 /*
5606  * Returns the number of days in a month.
5607  * Months that are |longMonths| always have 31 days.
5608  * Months that are not |longMonths| have 30 days except February (month 2).
5609  * February has 29 days during leap years which are years that are divisible by 400.
5610  * or divisible by 100 and 4. February has 28 days otherwise.
5611  */
5612 
5613   static const bool longMonths[] = { true, false, true, false, true, false,
5614                                      true, true, false, true, false, true };
5615   MOZ_ASSERT(aMonth <= 12 && aMonth > 0);
5616 
5617   if (longMonths[aMonth-1]) {
5618     return 31;
5619   }
5620 
5621   if (aMonth != 2) {
5622     return 30;
5623   }
5624 
5625   return IsLeapYear(aYear) ? 29 : 28;
5626 }
5627 
5628 /* static */ bool
DigitSubStringToNumber(const nsAString & aStr,uint32_t aStart,uint32_t aLen,uint32_t * aRetVal)5629 HTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
5630                                          uint32_t aStart, uint32_t aLen,
5631                                          uint32_t* aRetVal)
5632 {
5633   MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
5634 
5635   for (uint32_t offset = 0; offset < aLen; ++offset) {
5636     if (!NS_IsAsciiDigit(aStr[aStart + offset])) {
5637       return false;
5638     }
5639   }
5640 
5641   nsresult ec;
5642   *aRetVal = static_cast<uint32_t>(PromiseFlatString(Substring(aStr, aStart, aLen)).ToInteger(&ec));
5643 
5644   return NS_SUCCEEDED(ec);
5645 }
5646 
5647 bool
IsValidTime(const nsAString & aValue) const5648 HTMLInputElement::IsValidTime(const nsAString& aValue) const
5649 {
5650   return ParseTime(aValue, nullptr);
5651 }
5652 
5653 /* static */ bool
ParseTime(const nsAString & aValue,uint32_t * aResult)5654 HTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult)
5655 {
5656   /* The string must have the following parts:
5657    * - HOURS: two digits, value being in [0, 23];
5658    * - Colon (:);
5659    * - MINUTES: two digits, value being in [0, 59];
5660    * - Optional:
5661    *   - Colon (:);
5662    *   - SECONDS: two digits, value being in [0, 59];
5663    *   - Optional:
5664    *     - DOT (.);
5665    *     - FRACTIONAL SECONDS: one to three digits, no value range.
5666    */
5667 
5668   // The following format is the shorter one allowed: "HH:MM".
5669   if (aValue.Length() < 5) {
5670     return false;
5671   }
5672 
5673   uint32_t hours;
5674   if (!DigitSubStringToNumber(aValue, 0, 2, &hours) || hours > 23) {
5675     return false;
5676   }
5677 
5678   // Hours/minutes separator.
5679   if (aValue[2] != ':') {
5680     return false;
5681   }
5682 
5683   uint32_t minutes;
5684   if (!DigitSubStringToNumber(aValue, 3, 2, &minutes) || minutes > 59) {
5685     return false;
5686   }
5687 
5688   if (aValue.Length() == 5) {
5689     if (aResult) {
5690       *aResult = ((hours * 60) + minutes) * 60000;
5691     }
5692     return true;
5693   }
5694 
5695   // The following format is the next shorter one: "HH:MM:SS".
5696   if (aValue.Length() < 8 || aValue[5] != ':') {
5697     return false;
5698   }
5699 
5700   uint32_t seconds;
5701   if (!DigitSubStringToNumber(aValue, 6, 2, &seconds) || seconds > 59) {
5702     return false;
5703   }
5704 
5705   if (aValue.Length() == 8) {
5706     if (aResult) {
5707       *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000;
5708     }
5709     return true;
5710   }
5711 
5712   // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
5713   // There can be 1 to 3 digits for the fractions of seconds.
5714   if (aValue.Length() == 9 || aValue.Length() > 12 || aValue[8] != '.') {
5715     return false;
5716   }
5717 
5718   uint32_t fractionsSeconds;
5719   if (!DigitSubStringToNumber(aValue, 9, aValue.Length() - 9, &fractionsSeconds)) {
5720     return false;
5721   }
5722 
5723   if (aResult) {
5724     *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000 +
5725                // NOTE: there is 10.0 instead of 10 and static_cast<int> because
5726                // some old [and stupid] compilers can't just do the right thing.
5727                fractionsSeconds * pow(10.0, static_cast<int>(3 - (aValue.Length() - 9)));
5728   }
5729 
5730   return true;
5731 }
5732 
5733 static bool
IsDateTimeEnabled(int32_t aNewType)5734 IsDateTimeEnabled(int32_t aNewType)
5735 {
5736   return (aNewType == NS_FORM_INPUT_DATE &&
5737           (Preferences::GetBool("dom.forms.datetime", false) ||
5738            Preferences::GetBool("dom.experimental_forms", false) ||
5739            Preferences::GetBool("dom.forms.datepicker", false))) ||
5740          (aNewType == NS_FORM_INPUT_TIME &&
5741           (Preferences::GetBool("dom.forms.datetime", false) ||
5742            Preferences::GetBool("dom.experimental_forms", false))) ||
5743          ((aNewType == NS_FORM_INPUT_MONTH ||
5744            aNewType == NS_FORM_INPUT_WEEK ||
5745            aNewType == NS_FORM_INPUT_DATETIME_LOCAL) &&
5746           Preferences::GetBool("dom.forms.datetime", false));
5747 }
5748 
5749 bool
ParseAttribute(int32_t aNamespaceID,nsIAtom * aAttribute,const nsAString & aValue,nsAttrValue & aResult)5750 HTMLInputElement::ParseAttribute(int32_t aNamespaceID,
5751                                  nsIAtom* aAttribute,
5752                                  const nsAString& aValue,
5753                                  nsAttrValue& aResult)
5754 {
5755   if (aNamespaceID == kNameSpaceID_None) {
5756     if (aAttribute == nsGkAtoms::type) {
5757       // XXX ARG!! This is major evilness. ParseAttribute
5758       // shouldn't set members. Override SetAttr instead
5759       int32_t newType;
5760       bool success = aResult.ParseEnumValue(aValue, kInputTypeTable, false);
5761       if (success) {
5762         newType = aResult.GetEnumValue();
5763         if ((IsExperimentalMobileType(newType) &&
5764              !Preferences::GetBool("dom.experimental_forms", false)) ||
5765             (newType == NS_FORM_INPUT_NUMBER &&
5766              !Preferences::GetBool("dom.forms.number", false)) ||
5767             (newType == NS_FORM_INPUT_COLOR &&
5768              !Preferences::GetBool("dom.forms.color", false)) ||
5769             (IsDateTimeInputType(newType) && !IsDateTimeEnabled(newType))) {
5770           newType = kInputDefaultType->value;
5771           aResult.SetTo(newType, &aValue);
5772         }
5773       } else {
5774         newType = kInputDefaultType->value;
5775       }
5776 
5777       if (newType != mType) {
5778         // Make sure to do the check for newType being NS_FORM_INPUT_FILE and
5779         // the corresponding SetValueInternal() call _before_ we set mType.
5780         // That way the logic in SetValueInternal() will work right (that logic
5781         // makes assumptions about our frame based on mType, but we won't have
5782         // had time to recreate frames yet -- that happens later in the
5783         // SetAttr() process).
5784         if (newType == NS_FORM_INPUT_FILE || mType == NS_FORM_INPUT_FILE) {
5785           // This call isn't strictly needed any more since we'll never
5786           // confuse values and filenames. However it's there for backwards
5787           // compat.
5788           ClearFiles(false);
5789         }
5790 
5791         HandleTypeChange(newType);
5792       }
5793 
5794       return success;
5795     }
5796     if (aAttribute == nsGkAtoms::width) {
5797       return aResult.ParseSpecialIntValue(aValue);
5798     }
5799     if (aAttribute == nsGkAtoms::height) {
5800       return aResult.ParseSpecialIntValue(aValue);
5801     }
5802     if (aAttribute == nsGkAtoms::maxlength) {
5803       return aResult.ParseNonNegativeIntValue(aValue);
5804     }
5805     if (aAttribute == nsGkAtoms::minlength) {
5806       return aResult.ParseNonNegativeIntValue(aValue);
5807     }
5808     if (aAttribute == nsGkAtoms::size) {
5809       return aResult.ParsePositiveIntValue(aValue);
5810     }
5811     if (aAttribute == nsGkAtoms::border) {
5812       return aResult.ParseIntWithBounds(aValue, 0);
5813     }
5814     if (aAttribute == nsGkAtoms::align) {
5815       return ParseAlignValue(aValue, aResult);
5816     }
5817     if (aAttribute == nsGkAtoms::formmethod) {
5818       return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
5819     }
5820     if (aAttribute == nsGkAtoms::formenctype) {
5821       return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
5822     }
5823     if (aAttribute == nsGkAtoms::autocomplete) {
5824       aResult.ParseAtomArray(aValue);
5825       return true;
5826     }
5827     if (aAttribute == nsGkAtoms::inputmode) {
5828       return aResult.ParseEnumValue(aValue, kInputInputmodeTable, false);
5829     }
5830     if (ParseImageAttribute(aAttribute, aValue, aResult)) {
5831       // We have to call |ParseImageAttribute| unconditionally since we
5832       // don't know if we're going to have a type="image" attribute yet,
5833       // (or could have it set dynamically in the future).  See bug
5834       // 214077.
5835       return true;
5836     }
5837   }
5838 
5839   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
5840                                               aResult);
5841 }
5842 
5843 void
MapAttributesIntoRule(const nsMappedAttributes * aAttributes,nsRuleData * aData)5844 HTMLInputElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
5845                                         nsRuleData* aData)
5846 {
5847   const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::type);
5848   if (value && value->Type() == nsAttrValue::eEnum &&
5849       value->GetEnumValue() == NS_FORM_INPUT_IMAGE) {
5850     nsGenericHTMLFormElementWithState::MapImageBorderAttributeInto(aAttributes, aData);
5851     nsGenericHTMLFormElementWithState::MapImageMarginAttributeInto(aAttributes, aData);
5852     nsGenericHTMLFormElementWithState::MapImageSizeAttributesInto(aAttributes, aData);
5853     // Images treat align as "float"
5854     nsGenericHTMLFormElementWithState::MapImageAlignAttributeInto(aAttributes, aData);
5855   }
5856 
5857   nsGenericHTMLFormElementWithState::MapCommonAttributesInto(aAttributes, aData);
5858 }
5859 
5860 nsChangeHint
GetAttributeChangeHint(const nsIAtom * aAttribute,int32_t aModType) const5861 HTMLInputElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
5862                                          int32_t aModType) const
5863 {
5864   nsChangeHint retval =
5865     nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute, aModType);
5866   if (aAttribute == nsGkAtoms::type ||
5867       // The presence or absence of the 'directory' attribute determines what
5868       // buttons we show for type=file.
5869       aAttribute == nsGkAtoms::allowdirs ||
5870       aAttribute == nsGkAtoms::webkitdirectory) {
5871     retval |= nsChangeHint_ReconstructFrame;
5872   } else if (mType == NS_FORM_INPUT_IMAGE &&
5873              (aAttribute == nsGkAtoms::alt ||
5874               aAttribute == nsGkAtoms::value)) {
5875     // We might need to rebuild our alt text.  Just go ahead and
5876     // reconstruct our frame.  This should be quite rare..
5877     retval |= nsChangeHint_ReconstructFrame;
5878   } else if (aAttribute == nsGkAtoms::value) {
5879     retval |= NS_STYLE_HINT_REFLOW;
5880   } else if (aAttribute == nsGkAtoms::size &&
5881              IsSingleLineTextControl(false)) {
5882     retval |= NS_STYLE_HINT_REFLOW;
5883   } else if (PlaceholderApplies() && aAttribute == nsGkAtoms::placeholder) {
5884     retval |= nsChangeHint_ReconstructFrame;
5885   }
5886   return retval;
5887 }
5888 
NS_IMETHODIMP_(bool)5889 NS_IMETHODIMP_(bool)
5890 HTMLInputElement::IsAttributeMapped(const nsIAtom* aAttribute) const
5891 {
5892   static const MappedAttributeEntry attributes[] = {
5893     { &nsGkAtoms::align },
5894     { &nsGkAtoms::type },
5895     { nullptr },
5896   };
5897 
5898   static const MappedAttributeEntry* const map[] = {
5899     attributes,
5900     sCommonAttributeMap,
5901     sImageMarginSizeAttributeMap,
5902     sImageBorderAttributeMap,
5903   };
5904 
5905   return FindAttributeDependence(aAttribute, map);
5906 }
5907 
5908 nsMapRuleToAttributesFunc
GetAttributeMappingFunction() const5909 HTMLInputElement::GetAttributeMappingFunction() const
5910 {
5911   return &MapAttributesIntoRule;
5912 }
5913 
5914 
5915 // Directory picking methods:
5916 
5917 bool
IsFilesAndDirectoriesSupported() const5918 HTMLInputElement::IsFilesAndDirectoriesSupported() const
5919 {
5920   // This method is supposed to return true if a file and directory picker
5921   // supports the selection of both files and directories *at the same time*.
5922   // Only Mac currently supports that. We could implement it for Mac, but
5923   // currently we do not.
5924   return false;
5925 }
5926 
5927 void
ChooseDirectory(ErrorResult & aRv)5928 HTMLInputElement::ChooseDirectory(ErrorResult& aRv)
5929 {
5930   if (mType != NS_FORM_INPUT_FILE) {
5931     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5932     return;
5933   }
5934   // Script can call this method directly, so even though we don't show the
5935   // "Pick Folder..." button on platforms that don't have a directory picker
5936   // we have to redirect to the file picker here.
5937   InitFilePicker(
5938 #if defined(ANDROID) || defined(MOZ_B2G)
5939                  // No native directory picker - redirect to plain file picker
5940                  FILE_PICKER_FILE
5941 #else
5942                  FILE_PICKER_DIRECTORY
5943 #endif
5944                  );
5945 }
5946 
5947 already_AddRefed<Promise>
GetFilesAndDirectories(ErrorResult & aRv)5948 HTMLInputElement::GetFilesAndDirectories(ErrorResult& aRv)
5949 {
5950   if (mType != NS_FORM_INPUT_FILE) {
5951     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5952     return nullptr;
5953   }
5954 
5955   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
5956   MOZ_ASSERT(global);
5957   if (!global) {
5958     return nullptr;
5959   }
5960 
5961   RefPtr<Promise> p = Promise::Create(global, aRv);
5962   if (aRv.Failed()) {
5963     return nullptr;
5964   }
5965 
5966   const nsTArray<OwningFileOrDirectory>& filesAndDirs =
5967     GetFilesOrDirectoriesInternal();
5968 
5969   Sequence<OwningFileOrDirectory> filesAndDirsSeq;
5970 
5971   if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(),
5972                                  mozilla::fallible_t())) {
5973     p->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
5974     return p.forget();
5975   }
5976 
5977   for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) {
5978     if (filesAndDirs[i].IsDirectory()) {
5979       RefPtr<Directory> directory = filesAndDirs[i].GetAsDirectory();
5980 
5981       // In future we could refactor SetFilePickerFiltersFromAccept to return a
5982       // semicolon separated list of file extensions and include that in the
5983       // filter string passed here.
5984       directory->SetContentFilters(NS_LITERAL_STRING("filter-out-sensitive"));
5985       filesAndDirsSeq[i].SetAsDirectory() = directory;
5986     } else {
5987       MOZ_ASSERT(filesAndDirs[i].IsFile());
5988 
5989       // This file was directly selected by the user, so don't filter it.
5990       filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i].GetAsFile();
5991     }
5992   }
5993 
5994   p->MaybeResolve(filesAndDirsSeq);
5995   return p.forget();
5996 }
5997 
5998 already_AddRefed<Promise>
GetFiles(bool aRecursiveFlag,ErrorResult & aRv)5999 HTMLInputElement::GetFiles(bool aRecursiveFlag, ErrorResult& aRv)
6000 {
6001   if (mType != NS_FORM_INPUT_FILE) {
6002     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6003     return nullptr;
6004   }
6005 
6006   GetFilesHelper* helper = GetOrCreateGetFilesHelper(aRecursiveFlag, aRv);
6007   if (NS_WARN_IF(aRv.Failed())) {
6008     return nullptr;
6009   }
6010   MOZ_ASSERT(helper);
6011 
6012   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
6013   MOZ_ASSERT(global);
6014   if (!global) {
6015     return nullptr;
6016   }
6017 
6018   RefPtr<Promise> p = Promise::Create(global, aRv);
6019   if (aRv.Failed()) {
6020     return nullptr;
6021   }
6022 
6023   helper->AddPromise(p);
6024   return p.forget();
6025 }
6026 
6027 
6028 // Controllers Methods
6029 
6030 nsIControllers*
GetControllers(ErrorResult & aRv)6031 HTMLInputElement::GetControllers(ErrorResult& aRv)
6032 {
6033   //XXX: what about type "file"?
6034   if (IsSingleLineTextControl(false))
6035   {
6036     if (!mControllers)
6037     {
6038       nsresult rv;
6039       mControllers = do_CreateInstance(kXULControllersCID, &rv);
6040       if (NS_FAILED(rv)) {
6041         aRv.Throw(rv);
6042         return nullptr;
6043       }
6044 
6045       nsCOMPtr<nsIController>
6046         controller(do_CreateInstance("@mozilla.org/editor/editorcontroller;1",
6047                                      &rv));
6048       if (NS_FAILED(rv)) {
6049         aRv.Throw(rv);
6050         return nullptr;
6051       }
6052 
6053       mControllers->AppendController(controller);
6054 
6055       controller = do_CreateInstance("@mozilla.org/editor/editingcontroller;1",
6056                                      &rv);
6057       if (NS_FAILED(rv)) {
6058         aRv.Throw(rv);
6059         return nullptr;
6060       }
6061 
6062       mControllers->AppendController(controller);
6063     }
6064   }
6065 
6066   return mControllers;
6067 }
6068 
6069 NS_IMETHODIMP
GetControllers(nsIControllers ** aResult)6070 HTMLInputElement::GetControllers(nsIControllers** aResult)
6071 {
6072   NS_ENSURE_ARG_POINTER(aResult);
6073 
6074   ErrorResult rv;
6075   RefPtr<nsIControllers> controller = GetControllers(rv);
6076   controller.forget(aResult);
6077   return rv.StealNSResult();
6078 }
6079 
6080 int32_t
GetTextLength(ErrorResult & aRv)6081 HTMLInputElement::GetTextLength(ErrorResult& aRv)
6082 {
6083   nsAutoString val;
6084   GetValue(val);
6085   return val.Length();
6086 }
6087 
6088 NS_IMETHODIMP
GetTextLength(int32_t * aTextLength)6089 HTMLInputElement::GetTextLength(int32_t* aTextLength)
6090 {
6091   ErrorResult rv;
6092   *aTextLength = GetTextLength(rv);
6093   return rv.StealNSResult();
6094 }
6095 
6096 void
SetSelectionRange(int32_t aSelectionStart,int32_t aSelectionEnd,const Optional<nsAString> & aDirection,ErrorResult & aRv)6097 HTMLInputElement::SetSelectionRange(int32_t aSelectionStart,
6098                                     int32_t aSelectionEnd,
6099                                     const Optional<nsAString>& aDirection,
6100                                     ErrorResult& aRv)
6101 {
6102   if (!SupportsTextSelection()) {
6103     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6104     return;
6105   }
6106 
6107   nsresult rv = SetSelectionRange(aSelectionStart, aSelectionEnd,
6108     aDirection.WasPassed() ? aDirection.Value() : NullString());
6109 
6110   if (NS_FAILED(rv)) {
6111     aRv.Throw(rv);
6112   }
6113 }
6114 
6115 NS_IMETHODIMP
SetSelectionRange(int32_t aSelectionStart,int32_t aSelectionEnd,const nsAString & aDirection)6116 HTMLInputElement::SetSelectionRange(int32_t aSelectionStart,
6117                                     int32_t aSelectionEnd,
6118                                     const nsAString& aDirection)
6119 {
6120   nsresult rv = NS_OK;
6121   nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
6122   nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
6123   if (textControlFrame) {
6124     // Default to forward, even if not specified.
6125     // Note that we don't currently support directionless selections, so
6126     // "none" is treated like "forward".
6127     nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eForward;
6128     if (!aDirection.IsEmpty() && aDirection.EqualsLiteral("backward")) {
6129       dir = nsITextControlFrame::eBackward;
6130     }
6131 
6132     rv = textControlFrame->SetSelectionRange(aSelectionStart, aSelectionEnd, dir);
6133     if (NS_SUCCEEDED(rv)) {
6134       rv = textControlFrame->ScrollSelectionIntoView();
6135       RefPtr<AsyncEventDispatcher> asyncDispatcher =
6136         new AsyncEventDispatcher(this, NS_LITERAL_STRING("select"),
6137                                  true, false);
6138       asyncDispatcher->PostDOMEvent();
6139     }
6140   }
6141 
6142   return rv;
6143 }
6144 
6145 void
SetRangeText(const nsAString & aReplacement,ErrorResult & aRv)6146 HTMLInputElement::SetRangeText(const nsAString& aReplacement, ErrorResult& aRv)
6147 {
6148   if (!SupportsTextSelection()) {
6149     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6150     return;
6151   }
6152 
6153   int32_t start, end;
6154   aRv = GetSelectionRange(&start, &end);
6155   if (aRv.Failed()) {
6156     nsTextEditorState* state = GetEditorState();
6157     if (state && state->IsSelectionCached()) {
6158       start = state->GetSelectionProperties().GetStart();
6159       end = state->GetSelectionProperties().GetEnd();
6160       aRv = NS_OK;
6161     }
6162   }
6163 
6164   SetRangeText(aReplacement, start, end, mozilla::dom::SelectionMode::Preserve,
6165                aRv, start, end);
6166 }
6167 
6168 void
SetRangeText(const nsAString & aReplacement,uint32_t aStart,uint32_t aEnd,const SelectionMode & aSelectMode,ErrorResult & aRv,int32_t aSelectionStart,int32_t aSelectionEnd)6169 HTMLInputElement::SetRangeText(const nsAString& aReplacement, uint32_t aStart,
6170                                uint32_t aEnd, const SelectionMode& aSelectMode,
6171                                ErrorResult& aRv, int32_t aSelectionStart,
6172                                int32_t aSelectionEnd)
6173 {
6174   if (!SupportsTextSelection()) {
6175     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6176     return;
6177   }
6178 
6179   if (aStart > aEnd) {
6180     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
6181     return;
6182   }
6183 
6184   nsAutoString value;
6185   GetValueInternal(value);
6186   uint32_t inputValueLength = value.Length();
6187 
6188   if (aStart > inputValueLength) {
6189     aStart = inputValueLength;
6190   }
6191 
6192   if (aEnd > inputValueLength) {
6193     aEnd = inputValueLength;
6194   }
6195 
6196   if (aSelectionStart == -1 && aSelectionEnd == -1) {
6197     aRv = GetSelectionRange(&aSelectionStart, &aSelectionEnd);
6198     if (aRv.Failed()) {
6199       nsTextEditorState* state = GetEditorState();
6200       if (state && state->IsSelectionCached()) {
6201         aSelectionStart = state->GetSelectionProperties().GetStart();
6202         aSelectionEnd = state->GetSelectionProperties().GetEnd();
6203         aRv = NS_OK;
6204       }
6205     }
6206   }
6207 
6208   if (aStart <= aEnd) {
6209     value.Replace(aStart, aEnd - aStart, aReplacement);
6210     nsresult rv =
6211       SetValueInternal(value, nsTextEditorState::eSetValue_ByContent);
6212     if (NS_FAILED(rv)) {
6213       aRv.Throw(rv);
6214       return;
6215     }
6216   }
6217 
6218   uint32_t newEnd = aStart + aReplacement.Length();
6219   int32_t delta =  aReplacement.Length() - (aEnd - aStart);
6220 
6221   switch (aSelectMode) {
6222     case mozilla::dom::SelectionMode::Select:
6223     {
6224       aSelectionStart = aStart;
6225       aSelectionEnd = newEnd;
6226     }
6227     break;
6228     case mozilla::dom::SelectionMode::Start:
6229     {
6230       aSelectionStart = aSelectionEnd = aStart;
6231     }
6232     break;
6233     case mozilla::dom::SelectionMode::End:
6234     {
6235       aSelectionStart = aSelectionEnd = newEnd;
6236     }
6237     break;
6238     case mozilla::dom::SelectionMode::Preserve:
6239     {
6240       if ((uint32_t)aSelectionStart > aEnd) {
6241         aSelectionStart += delta;
6242       } else if ((uint32_t)aSelectionStart > aStart) {
6243         aSelectionStart = aStart;
6244       }
6245 
6246       if ((uint32_t)aSelectionEnd > aEnd) {
6247         aSelectionEnd += delta;
6248       } else if ((uint32_t)aSelectionEnd > aStart) {
6249         aSelectionEnd = newEnd;
6250       }
6251     }
6252     break;
6253     default:
6254       MOZ_CRASH("Unknown mode!");
6255   }
6256 
6257   Optional<nsAString> direction;
6258   SetSelectionRange(aSelectionStart, aSelectionEnd, direction, aRv);
6259 }
6260 
6261 Nullable<int32_t>
GetSelectionStart(ErrorResult & aRv)6262 HTMLInputElement::GetSelectionStart(ErrorResult& aRv)
6263 {
6264   if (!SupportsTextSelection()) {
6265     return Nullable<int32_t>();
6266   }
6267 
6268   int32_t selStart;
6269   nsresult rv = GetSelectionStart(&selStart);
6270   if (NS_FAILED(rv)) {
6271     aRv.Throw(rv);
6272   }
6273 
6274   return Nullable<int32_t>(selStart);
6275 }
6276 
6277 NS_IMETHODIMP
GetSelectionStart(int32_t * aSelectionStart)6278 HTMLInputElement::GetSelectionStart(int32_t* aSelectionStart)
6279 {
6280   NS_ENSURE_ARG_POINTER(aSelectionStart);
6281 
6282   int32_t selEnd, selStart;
6283   nsresult rv = GetSelectionRange(&selStart, &selEnd);
6284 
6285   if (NS_FAILED(rv)) {
6286     nsTextEditorState* state = GetEditorState();
6287     if (state && state->IsSelectionCached()) {
6288       *aSelectionStart = state->GetSelectionProperties().GetStart();
6289       return NS_OK;
6290     }
6291     return rv;
6292   }
6293 
6294   *aSelectionStart = selStart;
6295   return NS_OK;
6296 }
6297 
6298 void
SetSelectionStart(const Nullable<int32_t> & aSelectionStart,ErrorResult & aRv)6299 HTMLInputElement::SetSelectionStart(const Nullable<int32_t>& aSelectionStart,
6300                                     ErrorResult& aRv)
6301 {
6302   if (!SupportsTextSelection()) {
6303     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6304     return;
6305   }
6306 
6307   int32_t selStart = 0;
6308   if (!aSelectionStart.IsNull()) {
6309     selStart = aSelectionStart.Value();
6310   }
6311 
6312   nsTextEditorState* state = GetEditorState();
6313   if (state && state->IsSelectionCached()) {
6314     state->GetSelectionProperties().SetStart(selStart);
6315     return;
6316   }
6317 
6318   nsAutoString direction;
6319   aRv = GetSelectionDirection(direction);
6320   if (aRv.Failed()) {
6321     return;
6322   }
6323 
6324   int32_t start, end;
6325   aRv = GetSelectionRange(&start, &end);
6326   if (aRv.Failed()) {
6327     return;
6328   }
6329 
6330   start = selStart;
6331   if (end < start) {
6332     end = start;
6333   }
6334 
6335   aRv = SetSelectionRange(start, end, direction);
6336 }
6337 
6338 NS_IMETHODIMP
SetSelectionStart(int32_t aSelectionStart)6339 HTMLInputElement::SetSelectionStart(int32_t aSelectionStart)
6340 {
6341   ErrorResult rv;
6342   Nullable<int32_t> selStart(aSelectionStart);
6343   SetSelectionStart(selStart, rv);
6344   return rv.StealNSResult();
6345 }
6346 
6347 Nullable<int32_t>
GetSelectionEnd(ErrorResult & aRv)6348 HTMLInputElement::GetSelectionEnd(ErrorResult& aRv)
6349 {
6350   if (!SupportsTextSelection()) {
6351     return Nullable<int32_t>();
6352   }
6353 
6354   int32_t selEnd;
6355   nsresult rv = GetSelectionEnd(&selEnd);
6356   if (NS_FAILED(rv)) {
6357     aRv.Throw(rv);
6358   }
6359 
6360   return Nullable<int32_t>(selEnd);
6361 }
6362 
6363 NS_IMETHODIMP
GetSelectionEnd(int32_t * aSelectionEnd)6364 HTMLInputElement::GetSelectionEnd(int32_t* aSelectionEnd)
6365 {
6366   NS_ENSURE_ARG_POINTER(aSelectionEnd);
6367 
6368   int32_t selEnd, selStart;
6369   nsresult rv = GetSelectionRange(&selStart, &selEnd);
6370 
6371   if (NS_FAILED(rv)) {
6372     nsTextEditorState* state = GetEditorState();
6373     if (state && state->IsSelectionCached()) {
6374       *aSelectionEnd = state->GetSelectionProperties().GetEnd();
6375       return NS_OK;
6376     }
6377     return rv;
6378   }
6379 
6380   *aSelectionEnd = selEnd;
6381   return NS_OK;
6382 }
6383 
6384 void
SetSelectionEnd(const Nullable<int32_t> & aSelectionEnd,ErrorResult & aRv)6385 HTMLInputElement::SetSelectionEnd(const Nullable<int32_t>& aSelectionEnd,
6386                                   ErrorResult& aRv)
6387 {
6388   if (!SupportsTextSelection()) {
6389     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6390     return;
6391   }
6392 
6393   int32_t selEnd = 0;
6394   if (!aSelectionEnd.IsNull()) {
6395     selEnd = aSelectionEnd.Value();
6396   }
6397 
6398   nsTextEditorState* state = GetEditorState();
6399   if (state && state->IsSelectionCached()) {
6400     state->GetSelectionProperties().SetEnd(selEnd);
6401     return;
6402   }
6403 
6404   nsAutoString direction;
6405   aRv = GetSelectionDirection(direction);
6406   if (aRv.Failed()) {
6407     return;
6408   }
6409 
6410   int32_t start, end;
6411   aRv = GetSelectionRange(&start, &end);
6412   if (aRv.Failed()) {
6413     return;
6414   }
6415 
6416   end = selEnd;
6417   if (start > end) {
6418     start = end;
6419   }
6420 
6421   aRv = SetSelectionRange(start, end, direction);
6422 }
6423 
6424 NS_IMETHODIMP
SetSelectionEnd(int32_t aSelectionEnd)6425 HTMLInputElement::SetSelectionEnd(int32_t aSelectionEnd)
6426 {
6427   ErrorResult rv;
6428   Nullable<int32_t> selEnd(aSelectionEnd);
6429   SetSelectionEnd(selEnd, rv);
6430   return rv.StealNSResult();
6431 }
6432 
6433 NS_IMETHODIMP
GetFiles(nsIDOMFileList ** aFileList)6434 HTMLInputElement::GetFiles(nsIDOMFileList** aFileList)
6435 {
6436   RefPtr<FileList> list = GetFiles();
6437   list.forget(aFileList);
6438   return NS_OK;
6439 }
6440 
6441 nsresult
GetSelectionRange(int32_t * aSelectionStart,int32_t * aSelectionEnd)6442 HTMLInputElement::GetSelectionRange(int32_t* aSelectionStart,
6443                                     int32_t* aSelectionEnd)
6444 {
6445   nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
6446   nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
6447   if (textControlFrame) {
6448     return textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);
6449   }
6450 
6451   return NS_ERROR_FAILURE;
6452 }
6453 
6454 static void
DirectionToName(nsITextControlFrame::SelectionDirection dir,nsAString & aDirection)6455 DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
6456 {
6457   if (dir == nsITextControlFrame::eNone) {
6458     aDirection.AssignLiteral("none");
6459   } else if (dir == nsITextControlFrame::eForward) {
6460     aDirection.AssignLiteral("forward");
6461   } else if (dir == nsITextControlFrame::eBackward) {
6462     aDirection.AssignLiteral("backward");
6463   } else {
6464     NS_NOTREACHED("Invalid SelectionDirection value");
6465   }
6466 }
6467 
6468 void
GetSelectionDirection(nsAString & aDirection,ErrorResult & aRv)6469 HTMLInputElement::GetSelectionDirection(nsAString& aDirection, ErrorResult& aRv)
6470 {
6471   if (!SupportsTextSelection()) {
6472     aDirection.SetIsVoid(true);
6473     return;
6474   }
6475 
6476   nsresult rv = NS_ERROR_FAILURE;
6477   nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
6478   nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
6479   if (textControlFrame) {
6480     nsITextControlFrame::SelectionDirection dir;
6481     rv = textControlFrame->GetSelectionRange(nullptr, nullptr, &dir);
6482     if (NS_SUCCEEDED(rv)) {
6483       DirectionToName(dir, aDirection);
6484     }
6485   }
6486 
6487   if (NS_FAILED(rv)) {
6488     nsTextEditorState* state = GetEditorState();
6489     if (state && state->IsSelectionCached()) {
6490       DirectionToName(state->GetSelectionProperties().GetDirection(), aDirection);
6491       return;
6492     }
6493   }
6494 
6495   if (NS_FAILED(rv)) {
6496     aRv.Throw(rv);
6497   }
6498 }
6499 
6500 NS_IMETHODIMP
GetSelectionDirection(nsAString & aDirection)6501 HTMLInputElement::GetSelectionDirection(nsAString& aDirection)
6502 {
6503   ErrorResult rv;
6504   GetSelectionDirection(aDirection, rv);
6505   return rv.StealNSResult();
6506 }
6507 
6508 void
SetSelectionDirection(const nsAString & aDirection,ErrorResult & aRv)6509 HTMLInputElement::SetSelectionDirection(const nsAString& aDirection, ErrorResult& aRv)
6510 {
6511   if (!SupportsTextSelection()) {
6512     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6513     return;
6514   }
6515 
6516   nsTextEditorState* state = GetEditorState();
6517   if (state && state->IsSelectionCached()) {
6518     nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eNone;
6519     if (aDirection.EqualsLiteral("forward")) {
6520       dir = nsITextControlFrame::eForward;
6521     } else if (aDirection.EqualsLiteral("backward")) {
6522       dir = nsITextControlFrame::eBackward;
6523     }
6524     state->GetSelectionProperties().SetDirection(dir);
6525     return;
6526   }
6527 
6528   int32_t start, end;
6529   aRv = GetSelectionRange(&start, &end);
6530   if (!aRv.Failed()) {
6531     aRv = SetSelectionRange(start, end, aDirection);
6532   }
6533 }
6534 
6535 NS_IMETHODIMP
SetSelectionDirection(const nsAString & aDirection)6536 HTMLInputElement::SetSelectionDirection(const nsAString& aDirection)
6537 {
6538   ErrorResult rv;
6539   SetSelectionDirection(aDirection, rv);
6540   return rv.StealNSResult();
6541 }
6542 
6543 NS_IMETHODIMP
GetPhonetic(nsAString & aPhonetic)6544 HTMLInputElement::GetPhonetic(nsAString& aPhonetic)
6545 {
6546   aPhonetic.Truncate();
6547   nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
6548   nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
6549   if (textControlFrame) {
6550     textControlFrame->GetPhonetic(aPhonetic);
6551   }
6552 
6553   return NS_OK;
6554 }
6555 
6556 #ifdef ACCESSIBILITY
6557 /*static*/ nsresult
FireEventForAccessibility(nsIDOMHTMLInputElement * aTarget,nsPresContext * aPresContext,const nsAString & aEventType)6558 FireEventForAccessibility(nsIDOMHTMLInputElement* aTarget,
6559                           nsPresContext* aPresContext,
6560                           const nsAString& aEventType)
6561 {
6562   nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aTarget);
6563   RefPtr<Event> event = NS_NewDOMEvent(element, aPresContext, nullptr);
6564   event->InitEvent(aEventType, true, true);
6565   event->SetTrusted(true);
6566 
6567   EventDispatcher::DispatchDOMEvent(aTarget, nullptr, event, aPresContext,
6568                                     nullptr);
6569 
6570   return NS_OK;
6571 }
6572 #endif
6573 
6574 void
UpdateApzAwareFlag()6575 HTMLInputElement::UpdateApzAwareFlag()
6576 {
6577 #if !defined(ANDROID) && !defined(XP_MACOSX)
6578   if ((mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE)) {
6579     SetMayBeApzAware();
6580   }
6581 #endif
6582 }
6583 
6584 nsresult
SetDefaultValueAsValue()6585 HTMLInputElement::SetDefaultValueAsValue()
6586 {
6587   NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE,
6588                "GetValueMode() should return VALUE_MODE_VALUE!");
6589 
6590   // The element has a content attribute value different from it's value when
6591   // it's in the value mode value.
6592   nsAutoString resetVal;
6593   GetDefaultValue(resetVal);
6594 
6595   // SetValueInternal is going to sanitize the value.
6596   return SetValueInternal(resetVal, nsTextEditorState::eSetValue_Internal);
6597 }
6598 
6599 void
SetDirectionIfAuto(bool aAuto,bool aNotify)6600 HTMLInputElement::SetDirectionIfAuto(bool aAuto, bool aNotify)
6601 {
6602   if (aAuto) {
6603     SetHasDirAuto();
6604     if (IsSingleLineTextControl(true)) {
6605       nsAutoString value;
6606       GetValue(value);
6607       SetDirectionalityFromValue(this, value, aNotify);
6608     }
6609   } else {
6610     ClearHasDirAuto();
6611   }
6612 }
6613 
6614 NS_IMETHODIMP
Reset()6615 HTMLInputElement::Reset()
6616 {
6617   // We should be able to reset all dirty flags regardless of the type.
6618   SetCheckedChanged(false);
6619   SetValueChanged(false);
6620   mLastValueChangeWasInteractive = false;
6621 
6622   switch (GetValueMode()) {
6623     case VALUE_MODE_VALUE:
6624       return SetDefaultValueAsValue();
6625     case VALUE_MODE_DEFAULT_ON:
6626       DoSetChecked(DefaultChecked(), true, false);
6627       return NS_OK;
6628     case VALUE_MODE_FILENAME:
6629       ClearFiles(false);
6630       return NS_OK;
6631     case VALUE_MODE_DEFAULT:
6632     default:
6633       return NS_OK;
6634   }
6635 }
6636 
6637 NS_IMETHODIMP
SubmitNamesValues(HTMLFormSubmission * aFormSubmission)6638 HTMLInputElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
6639 {
6640   // Disabled elements don't submit
6641   // For type=reset, and type=button, we just never submit, period.
6642   // For type=image and type=button, we only submit if we were the button
6643   // pressed
6644   // For type=radio and type=checkbox, we only submit if checked=true
6645   if (IsDisabled() || mType == NS_FORM_INPUT_RESET ||
6646       mType == NS_FORM_INPUT_BUTTON ||
6647       ((mType == NS_FORM_INPUT_SUBMIT || mType == NS_FORM_INPUT_IMAGE) &&
6648        aFormSubmission->GetOriginatingElement() != this) ||
6649       ((mType == NS_FORM_INPUT_RADIO || mType == NS_FORM_INPUT_CHECKBOX) &&
6650        !mChecked)) {
6651     return NS_OK;
6652   }
6653 
6654   // Get the name
6655   nsAutoString name;
6656   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6657 
6658   // Submit .x, .y for input type=image
6659   if (mType == NS_FORM_INPUT_IMAGE) {
6660     // Get a property set by the frame to find out where it was clicked.
6661     nsIntPoint* lastClickedPoint =
6662       static_cast<nsIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
6663     int32_t x, y;
6664     if (lastClickedPoint) {
6665       // Convert the values to strings for submission
6666       x = lastClickedPoint->x;
6667       y = lastClickedPoint->y;
6668     } else {
6669       x = y = 0;
6670     }
6671 
6672     nsAutoString xVal, yVal;
6673     xVal.AppendInt(x);
6674     yVal.AppendInt(y);
6675 
6676     if (!name.IsEmpty()) {
6677       aFormSubmission->AddNameValuePair(name + NS_LITERAL_STRING(".x"), xVal);
6678       aFormSubmission->AddNameValuePair(name + NS_LITERAL_STRING(".y"), yVal);
6679     } else {
6680       // If the Image Element has no name, simply return x and y
6681       // to Nav and IE compatibility.
6682       aFormSubmission->AddNameValuePair(NS_LITERAL_STRING("x"), xVal);
6683       aFormSubmission->AddNameValuePair(NS_LITERAL_STRING("y"), yVal);
6684     }
6685 
6686     return NS_OK;
6687   }
6688 
6689   //
6690   // Submit name=value
6691   //
6692 
6693   // If name not there, don't submit
6694   if (name.IsEmpty()) {
6695     return NS_OK;
6696   }
6697 
6698   // Get the value
6699   nsAutoString value;
6700   nsresult rv = GetValue(value);
6701   if (NS_FAILED(rv)) {
6702     return rv;
6703   }
6704 
6705   if (mType == NS_FORM_INPUT_SUBMIT && value.IsEmpty() &&
6706       !HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
6707     // Get our default value, which is the same as our default label
6708     nsXPIDLString defaultValue;
6709     nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
6710                                        "Submit", defaultValue);
6711     value = defaultValue;
6712   }
6713 
6714   //
6715   // Submit file if its input type=file and this encoding method accepts files
6716   //
6717   if (mType == NS_FORM_INPUT_FILE) {
6718     // Submit files
6719 
6720     const nsTArray<OwningFileOrDirectory>& files =
6721       GetFilesOrDirectoriesInternal();
6722 
6723     if (files.IsEmpty()) {
6724       aFormSubmission->AddNameBlobOrNullPair(name, nullptr);
6725       return NS_OK;
6726     }
6727 
6728     for (uint32_t i = 0; i < files.Length(); ++i) {
6729       if (files[i].IsFile()) {
6730         aFormSubmission->AddNameBlobOrNullPair(name, files[i].GetAsFile());
6731       } else {
6732         MOZ_ASSERT(files[i].IsDirectory());
6733         aFormSubmission->AddNameDirectoryPair(name, files[i].GetAsDirectory());
6734       }
6735     }
6736 
6737     return NS_OK;
6738   }
6739 
6740   if (mType == NS_FORM_INPUT_HIDDEN && name.EqualsLiteral("_charset_")) {
6741     nsCString charset;
6742     aFormSubmission->GetCharset(charset);
6743     return aFormSubmission->AddNameValuePair(name,
6744                                              NS_ConvertASCIItoUTF16(charset));
6745   }
6746   if (IsSingleLineTextControl(true) &&
6747       name.EqualsLiteral("isindex") &&
6748       aFormSubmission->SupportsIsindexSubmission()) {
6749     return aFormSubmission->AddIsindex(value);
6750   }
6751   return aFormSubmission->AddNameValuePair(name, value);
6752 }
6753 
6754 
6755 NS_IMETHODIMP
SaveState()6756 HTMLInputElement::SaveState()
6757 {
6758   RefPtr<HTMLInputElementState> inputState;
6759   switch (GetValueMode()) {
6760     case VALUE_MODE_DEFAULT_ON:
6761       if (mCheckedChanged) {
6762         inputState = new HTMLInputElementState();
6763         inputState->SetChecked(mChecked);
6764       }
6765       break;
6766     case VALUE_MODE_FILENAME:
6767       if (!mFilesOrDirectories.IsEmpty()) {
6768         inputState = new HTMLInputElementState();
6769         inputState->SetFilesOrDirectories(mFilesOrDirectories);
6770       }
6771       break;
6772     case VALUE_MODE_VALUE:
6773     case VALUE_MODE_DEFAULT:
6774       // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
6775       // mType shouldn't be NS_FORM_INPUT_PASSWORD and value should have changed.
6776       if ((GetValueMode() == VALUE_MODE_DEFAULT &&
6777            mType != NS_FORM_INPUT_HIDDEN) ||
6778           mType == NS_FORM_INPUT_PASSWORD || !mValueChanged) {
6779         break;
6780       }
6781 
6782       inputState = new HTMLInputElementState();
6783       nsAutoString value;
6784       nsresult rv = GetValue(value);
6785       if (NS_FAILED(rv)) {
6786         return rv;
6787       }
6788 
6789       if (!IsSingleLineTextControl(false)) {
6790         rv = nsLinebreakConverter::ConvertStringLineBreaks(
6791                value,
6792                nsLinebreakConverter::eLinebreakPlatform,
6793                nsLinebreakConverter::eLinebreakContent);
6794 
6795         if (NS_FAILED(rv)) {
6796           NS_ERROR("Converting linebreaks failed!");
6797           return rv;
6798         }
6799       }
6800 
6801       inputState->SetValue(value);
6802       break;
6803   }
6804 
6805   if (inputState) {
6806     nsPresState* state = GetPrimaryPresState();
6807     if (state) {
6808       state->SetStateProperty(inputState);
6809     }
6810   }
6811 
6812   if (mDisabledChanged) {
6813     nsPresState* state = GetPrimaryPresState();
6814     if (state) {
6815       // We do not want to save the real disabled state but the disabled
6816       // attribute.
6817       state->SetDisabled(HasAttr(kNameSpaceID_None, nsGkAtoms::disabled));
6818     }
6819   }
6820 
6821   return NS_OK;
6822 }
6823 
6824 void
DoneCreatingElement()6825 HTMLInputElement::DoneCreatingElement()
6826 {
6827   mDoneCreating = true;
6828 
6829   //
6830   // Restore state as needed.  Note that disabled state applies to all control
6831   // types.
6832   //
6833   bool restoredCheckedState =
6834     !mInhibitRestoration && NS_SUCCEEDED(GenerateStateKey()) && RestoreFormControlState();
6835 
6836   //
6837   // If restore does not occur, we initialize .checked using the CHECKED
6838   // property.
6839   //
6840   if (!restoredCheckedState && mShouldInitChecked) {
6841     DoSetChecked(DefaultChecked(), false, true);
6842     DoSetCheckedChanged(false, false);
6843   }
6844 
6845   // Sanitize the value.
6846   if (GetValueMode() == VALUE_MODE_VALUE) {
6847     nsAutoString aValue;
6848     GetValue(aValue);
6849     // TODO: What should we do if SetValueInternal fails?  (The allocation
6850     // may potentially be big, but most likely we've failed to allocate
6851     // before the type change.)
6852     SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
6853   }
6854 
6855   mShouldInitChecked = false;
6856 }
6857 
6858 EventStates
IntrinsicState() const6859 HTMLInputElement::IntrinsicState() const
6860 {
6861   // If you add states here, and they're type-dependent, you need to add them
6862   // to the type case in AfterSetAttr.
6863 
6864   EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
6865   if (mType == NS_FORM_INPUT_CHECKBOX || mType == NS_FORM_INPUT_RADIO) {
6866     // Check current checked state (:checked)
6867     if (mChecked) {
6868       state |= NS_EVENT_STATE_CHECKED;
6869     }
6870 
6871     // Check current indeterminate state (:indeterminate)
6872     if (mType == NS_FORM_INPUT_CHECKBOX && mIndeterminate) {
6873       state |= NS_EVENT_STATE_INDETERMINATE;
6874     }
6875 
6876     if (mType == NS_FORM_INPUT_RADIO) {
6877       nsCOMPtr<nsIDOMHTMLInputElement> selected = GetSelectedRadioButton();
6878       bool indeterminate = !selected && !mChecked;
6879 
6880       if (indeterminate) {
6881         state |= NS_EVENT_STATE_INDETERMINATE;
6882       }
6883     }
6884 
6885     // Check whether we are the default checked element (:default)
6886     if (DefaultChecked()) {
6887       state |= NS_EVENT_STATE_DEFAULT;
6888     }
6889   } else if (mType == NS_FORM_INPUT_IMAGE) {
6890     state |= nsImageLoadingContent::ImageState();
6891   }
6892 
6893   if (DoesRequiredApply() && HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
6894     state |= NS_EVENT_STATE_REQUIRED;
6895   } else {
6896     state |= NS_EVENT_STATE_OPTIONAL;
6897   }
6898 
6899   if (IsCandidateForConstraintValidation()) {
6900     if (IsValid()) {
6901       state |= NS_EVENT_STATE_VALID;
6902     } else {
6903       state |= NS_EVENT_STATE_INVALID;
6904 
6905       if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
6906           (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
6907            (mCanShowInvalidUI && ShouldShowValidityUI()))) {
6908         state |= NS_EVENT_STATE_MOZ_UI_INVALID;
6909       }
6910     }
6911 
6912     // :-moz-ui-valid applies if all of the following conditions are true:
6913     // 1. The element is not focused, or had either :-moz-ui-valid or
6914     //    :-moz-ui-invalid applying before it was focused ;
6915     // 2. The element is either valid or isn't allowed to have
6916     //    :-moz-ui-invalid applying ;
6917     // 3. The element has no form owner or its form owner doesn't have the
6918     //    novalidate attribute set ;
6919     // 4. The element has already been modified or the user tried to submit the
6920     //    form owner while invalid.
6921     if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
6922         (mCanShowValidUI && ShouldShowValidityUI() &&
6923          (IsValid() || (!state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
6924                         !mCanShowInvalidUI)))) {
6925       state |= NS_EVENT_STATE_MOZ_UI_VALID;
6926     }
6927 
6928     // :in-range and :out-of-range only apply if the element currently has a range
6929     if (mHasRange) {
6930       state |= (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
6931                 GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW))
6932                  ? NS_EVENT_STATE_OUTOFRANGE
6933                  : NS_EVENT_STATE_INRANGE;
6934     }
6935   }
6936 
6937   if (PlaceholderApplies() &&
6938       HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
6939       IsValueEmpty()) {
6940     state |= NS_EVENT_STATE_PLACEHOLDERSHOWN;
6941   }
6942 
6943   if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
6944     state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
6945   }
6946 
6947   return state;
6948 }
6949 
6950 void
AddStates(EventStates aStates)6951 HTMLInputElement::AddStates(EventStates aStates)
6952 {
6953   if (mType == NS_FORM_INPUT_TEXT) {
6954     EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
6955                                        NS_EVENT_STATE_FOCUSRING));
6956     if (!focusStates.IsEmpty()) {
6957       HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
6958       if (ownerNumberControl) {
6959         ownerNumberControl->AddStates(focusStates);
6960       } else {
6961         HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
6962         if (ownerDateTimeControl) {
6963           ownerDateTimeControl->AddStates(focusStates);
6964         }
6965       }
6966     }
6967   }
6968   nsGenericHTMLFormElementWithState::AddStates(aStates);
6969 }
6970 
6971 void
RemoveStates(EventStates aStates)6972 HTMLInputElement::RemoveStates(EventStates aStates)
6973 {
6974   if (mType == NS_FORM_INPUT_TEXT) {
6975     EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
6976                                        NS_EVENT_STATE_FOCUSRING));
6977     if (!focusStates.IsEmpty()) {
6978       HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
6979       if (ownerNumberControl) {
6980         ownerNumberControl->RemoveStates(focusStates);
6981       } else {
6982         HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
6983         if (ownerDateTimeControl) {
6984           ownerDateTimeControl->RemoveStates(focusStates);
6985         }
6986       }
6987     }
6988   }
6989   nsGenericHTMLFormElementWithState::RemoveStates(aStates);
6990 }
6991 
6992 bool
RestoreState(nsPresState * aState)6993 HTMLInputElement::RestoreState(nsPresState* aState)
6994 {
6995   bool restoredCheckedState = false;
6996 
6997   nsCOMPtr<HTMLInputElementState> inputState
6998     (do_QueryInterface(aState->GetStateProperty()));
6999 
7000   if (inputState) {
7001     switch (GetValueMode()) {
7002       case VALUE_MODE_DEFAULT_ON:
7003         if (inputState->IsCheckedSet()) {
7004           restoredCheckedState = true;
7005           DoSetChecked(inputState->GetChecked(), true, true);
7006         }
7007         break;
7008       case VALUE_MODE_FILENAME:
7009         {
7010           nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
7011           if (window) {
7012             nsTArray<OwningFileOrDirectory> array;
7013             inputState->GetFilesOrDirectories(window, array);
7014 
7015             SetFilesOrDirectories(array, true);
7016           }
7017         }
7018         break;
7019       case VALUE_MODE_VALUE:
7020       case VALUE_MODE_DEFAULT:
7021         if (GetValueMode() == VALUE_MODE_DEFAULT &&
7022             mType != NS_FORM_INPUT_HIDDEN) {
7023           break;
7024         }
7025 
7026         // TODO: What should we do if SetValueInternal fails?  (The allocation
7027         // may potentially be big, but most likely we've failed to allocate
7028         // before the type change.)
7029         SetValueInternal(inputState->GetValue(),
7030                          nsTextEditorState::eSetValue_Notify);
7031         break;
7032     }
7033   }
7034 
7035   if (aState->IsDisabledSet()) {
7036     SetDisabled(aState->GetDisabled());
7037   }
7038 
7039   return restoredCheckedState;
7040 }
7041 
7042 bool
AllowDrop()7043 HTMLInputElement::AllowDrop()
7044 {
7045   // Allow drop on anything other than file inputs.
7046 
7047   return mType != NS_FORM_INPUT_FILE;
7048 }
7049 
7050 /*
7051  * Radio group stuff
7052  */
7053 
7054 void
AddedToRadioGroup()7055 HTMLInputElement::AddedToRadioGroup()
7056 {
7057   // If the element is neither in a form nor a document, there is no group so we
7058   // should just stop here.
7059   if (!mForm && !IsInUncomposedDoc()) {
7060     return;
7061   }
7062 
7063   // Make sure not to notify if we're still being created
7064   bool notify = mDoneCreating;
7065 
7066   //
7067   // If the input element is checked, and we add it to the group, it will
7068   // deselect whatever is currently selected in that group
7069   //
7070   if (mChecked) {
7071     //
7072     // If it is checked, call "RadioSetChecked" to perform the selection/
7073     // deselection ritual.  This has the side effect of repainting the
7074     // radio button, but as adding a checked radio button into the group
7075     // should not be that common an occurrence, I think we can live with
7076     // that.
7077     //
7078     RadioSetChecked(notify);
7079   }
7080 
7081   //
7082   // For integrity purposes, we have to ensure that "checkedChanged" is
7083   // the same for this new element as for all the others in the group
7084   //
7085   bool checkedChanged = mCheckedChanged;
7086 
7087   nsCOMPtr<nsIRadioVisitor> visitor =
7088     new nsRadioGetCheckedChangedVisitor(&checkedChanged, this);
7089   VisitGroup(visitor, notify);
7090 
7091   SetCheckedChangedInternal(checkedChanged);
7092 
7093   //
7094   // Add the radio to the radio group container.
7095   //
7096   nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
7097   if (container) {
7098     nsAutoString name;
7099     GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
7100     container->AddToRadioGroup(name, static_cast<nsIFormControl*>(this));
7101 
7102     // We initialize the validity of the element to the validity of the group
7103     // because we assume UpdateValueMissingState() will be called after.
7104     SetValidityState(VALIDITY_STATE_VALUE_MISSING,
7105                      container->GetValueMissingState(name));
7106   }
7107 }
7108 
7109 void
WillRemoveFromRadioGroup()7110 HTMLInputElement::WillRemoveFromRadioGroup()
7111 {
7112   nsIRadioGroupContainer* container = GetRadioGroupContainer();
7113   if (!container) {
7114     return;
7115   }
7116 
7117   nsAutoString name;
7118   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
7119 
7120   // If this button was checked, we need to notify the group that there is no
7121   // longer a selected radio button
7122   if (mChecked) {
7123     container->SetCurrentRadioButton(name, nullptr);
7124 
7125     nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
7126     VisitGroup(visitor, true);
7127   }
7128 
7129   // Remove this radio from its group in the container.
7130   // We need to call UpdateValueMissingValidityStateForRadio before to make sure
7131   // the group validity is updated (with this element being ignored).
7132   UpdateValueMissingValidityStateForRadio(true);
7133   container->RemoveFromRadioGroup(name, static_cast<nsIFormControl*>(this));
7134 }
7135 
7136 bool
IsHTMLFocusable(bool aWithMouse,bool * aIsFocusable,int32_t * aTabIndex)7137 HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t* aTabIndex)
7138 {
7139   if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(aWithMouse, aIsFocusable,
7140       aTabIndex))
7141   {
7142     return true;
7143   }
7144 
7145   if (IsDisabled()) {
7146     *aIsFocusable = false;
7147     return true;
7148   }
7149 
7150   if (IsSingleLineTextControl(false) ||
7151       mType == NS_FORM_INPUT_RANGE) {
7152     *aIsFocusable = true;
7153     return false;
7154   }
7155 
7156 #ifdef XP_MACOSX
7157   const bool defaultFocusable = !aWithMouse || nsFocusManager::sMouseFocusesFormControl;
7158 #else
7159   const bool defaultFocusable = true;
7160 #endif
7161 
7162   if (mType == NS_FORM_INPUT_FILE ||
7163       mType == NS_FORM_INPUT_NUMBER ||
7164       mType == NS_FORM_INPUT_TIME) {
7165     if (aTabIndex) {
7166       // We only want our native anonymous child to be tabable to, not ourself.
7167       *aTabIndex = -1;
7168     }
7169     if (mType == NS_FORM_INPUT_NUMBER ||
7170         mType == NS_FORM_INPUT_TIME) {
7171       *aIsFocusable = true;
7172     } else {
7173       *aIsFocusable = defaultFocusable;
7174     }
7175     return true;
7176   }
7177 
7178   if (mType == NS_FORM_INPUT_HIDDEN) {
7179     if (aTabIndex) {
7180       *aTabIndex = -1;
7181     }
7182     *aIsFocusable = false;
7183     return false;
7184   }
7185 
7186   if (!aTabIndex) {
7187     // The other controls are all focusable
7188     *aIsFocusable = defaultFocusable;
7189     return false;
7190   }
7191 
7192   if (mType != NS_FORM_INPUT_RADIO) {
7193     *aIsFocusable = defaultFocusable;
7194     return false;
7195   }
7196 
7197   if (mChecked) {
7198     // Selected radio buttons are tabbable
7199     *aIsFocusable = defaultFocusable;
7200     return false;
7201   }
7202 
7203   // Current radio button is not selected.
7204   // But make it tabbable if nothing in group is selected.
7205   nsIRadioGroupContainer* container = GetRadioGroupContainer();
7206   if (!container) {
7207     *aIsFocusable = defaultFocusable;
7208     return false;
7209   }
7210 
7211   nsAutoString name;
7212   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
7213 
7214   if (container->GetCurrentRadioButton(name)) {
7215     *aTabIndex = -1;
7216   }
7217   *aIsFocusable = defaultFocusable;
7218   return false;
7219 }
7220 
7221 nsresult
VisitGroup(nsIRadioVisitor * aVisitor,bool aFlushContent)7222 HTMLInputElement::VisitGroup(nsIRadioVisitor* aVisitor, bool aFlushContent)
7223 {
7224   nsIRadioGroupContainer* container = GetRadioGroupContainer();
7225   if (container) {
7226     nsAutoString name;
7227     GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
7228     return container->WalkRadioGroup(name, aVisitor, aFlushContent);
7229   }
7230 
7231   aVisitor->Visit(this);
7232   return NS_OK;
7233 }
7234 
7235 HTMLInputElement::ValueModeType
GetValueMode() const7236 HTMLInputElement::GetValueMode() const
7237 {
7238   switch (mType)
7239   {
7240     case NS_FORM_INPUT_HIDDEN:
7241     case NS_FORM_INPUT_SUBMIT:
7242     case NS_FORM_INPUT_BUTTON:
7243     case NS_FORM_INPUT_RESET:
7244     case NS_FORM_INPUT_IMAGE:
7245       return VALUE_MODE_DEFAULT;
7246     case NS_FORM_INPUT_CHECKBOX:
7247     case NS_FORM_INPUT_RADIO:
7248       return VALUE_MODE_DEFAULT_ON;
7249     case NS_FORM_INPUT_FILE:
7250       return VALUE_MODE_FILENAME;
7251 #ifdef DEBUG
7252     case NS_FORM_INPUT_TEXT:
7253     case NS_FORM_INPUT_PASSWORD:
7254     case NS_FORM_INPUT_SEARCH:
7255     case NS_FORM_INPUT_TEL:
7256     case NS_FORM_INPUT_EMAIL:
7257     case NS_FORM_INPUT_URL:
7258     case NS_FORM_INPUT_NUMBER:
7259     case NS_FORM_INPUT_RANGE:
7260     case NS_FORM_INPUT_DATE:
7261     case NS_FORM_INPUT_TIME:
7262     case NS_FORM_INPUT_COLOR:
7263     case NS_FORM_INPUT_MONTH:
7264     case NS_FORM_INPUT_WEEK:
7265     case NS_FORM_INPUT_DATETIME_LOCAL:
7266       return VALUE_MODE_VALUE;
7267     default:
7268       NS_NOTYETIMPLEMENTED("Unexpected input type in GetValueMode()");
7269       return VALUE_MODE_VALUE;
7270 #else // DEBUG
7271     default:
7272       return VALUE_MODE_VALUE;
7273 #endif // DEBUG
7274   }
7275 }
7276 
7277 bool
IsMutable() const7278 HTMLInputElement::IsMutable() const
7279 {
7280   return !IsDisabled() &&
7281          !(DoesReadOnlyApply() &&
7282            HasAttr(kNameSpaceID_None, nsGkAtoms::readonly));
7283 }
7284 
7285 bool
DoesReadOnlyApply() const7286 HTMLInputElement::DoesReadOnlyApply() const
7287 {
7288   switch (mType)
7289   {
7290     case NS_FORM_INPUT_HIDDEN:
7291     case NS_FORM_INPUT_BUTTON:
7292     case NS_FORM_INPUT_IMAGE:
7293     case NS_FORM_INPUT_RESET:
7294     case NS_FORM_INPUT_SUBMIT:
7295     case NS_FORM_INPUT_RADIO:
7296     case NS_FORM_INPUT_FILE:
7297     case NS_FORM_INPUT_CHECKBOX:
7298     case NS_FORM_INPUT_RANGE:
7299     case NS_FORM_INPUT_COLOR:
7300       return false;
7301 #ifdef DEBUG
7302     case NS_FORM_INPUT_TEXT:
7303     case NS_FORM_INPUT_PASSWORD:
7304     case NS_FORM_INPUT_SEARCH:
7305     case NS_FORM_INPUT_TEL:
7306     case NS_FORM_INPUT_EMAIL:
7307     case NS_FORM_INPUT_URL:
7308     case NS_FORM_INPUT_NUMBER:
7309     case NS_FORM_INPUT_DATE:
7310     case NS_FORM_INPUT_TIME:
7311     case NS_FORM_INPUT_MONTH:
7312     case NS_FORM_INPUT_WEEK:
7313     case NS_FORM_INPUT_DATETIME_LOCAL:
7314       return true;
7315     default:
7316       NS_NOTYETIMPLEMENTED("Unexpected input type in DoesReadOnlyApply()");
7317       return true;
7318 #else // DEBUG
7319     default:
7320       return true;
7321 #endif // DEBUG
7322   }
7323 }
7324 
7325 bool
DoesRequiredApply() const7326 HTMLInputElement::DoesRequiredApply() const
7327 {
7328   switch (mType)
7329   {
7330     case NS_FORM_INPUT_HIDDEN:
7331     case NS_FORM_INPUT_BUTTON:
7332     case NS_FORM_INPUT_IMAGE:
7333     case NS_FORM_INPUT_RESET:
7334     case NS_FORM_INPUT_SUBMIT:
7335     case NS_FORM_INPUT_RANGE:
7336     case NS_FORM_INPUT_COLOR:
7337       return false;
7338 #ifdef DEBUG
7339     case NS_FORM_INPUT_RADIO:
7340     case NS_FORM_INPUT_CHECKBOX:
7341     case NS_FORM_INPUT_FILE:
7342     case NS_FORM_INPUT_TEXT:
7343     case NS_FORM_INPUT_PASSWORD:
7344     case NS_FORM_INPUT_SEARCH:
7345     case NS_FORM_INPUT_TEL:
7346     case NS_FORM_INPUT_EMAIL:
7347     case NS_FORM_INPUT_URL:
7348     case NS_FORM_INPUT_NUMBER:
7349     case NS_FORM_INPUT_DATE:
7350     case NS_FORM_INPUT_TIME:
7351     case NS_FORM_INPUT_MONTH:
7352     case NS_FORM_INPUT_WEEK:
7353     case NS_FORM_INPUT_DATETIME_LOCAL:
7354       return true;
7355     default:
7356       NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
7357       return true;
7358 #else // DEBUG
7359     default:
7360       return true;
7361 #endif // DEBUG
7362   }
7363 }
7364 
7365 bool
PlaceholderApplies() const7366 HTMLInputElement::PlaceholderApplies() const
7367 {
7368   if (IsDateTimeInputType(mType)) {
7369     return false;
7370   }
7371 
7372   return IsSingleLineTextControl(false);
7373 }
7374 
7375 bool
DoesPatternApply() const7376 HTMLInputElement::DoesPatternApply() const
7377 {
7378   // TODO: temporary until bug 773205 is fixed.
7379   if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
7380     return false;
7381   }
7382 
7383   return IsSingleLineTextControl(false);
7384 }
7385 
7386 bool
DoesMinMaxApply() const7387 HTMLInputElement::DoesMinMaxApply() const
7388 {
7389   switch (mType)
7390   {
7391     case NS_FORM_INPUT_NUMBER:
7392     case NS_FORM_INPUT_DATE:
7393     case NS_FORM_INPUT_TIME:
7394     case NS_FORM_INPUT_RANGE:
7395     case NS_FORM_INPUT_MONTH:
7396     case NS_FORM_INPUT_WEEK:
7397     case NS_FORM_INPUT_DATETIME_LOCAL:
7398       return true;
7399 #ifdef DEBUG
7400     case NS_FORM_INPUT_RESET:
7401     case NS_FORM_INPUT_SUBMIT:
7402     case NS_FORM_INPUT_IMAGE:
7403     case NS_FORM_INPUT_BUTTON:
7404     case NS_FORM_INPUT_HIDDEN:
7405     case NS_FORM_INPUT_RADIO:
7406     case NS_FORM_INPUT_CHECKBOX:
7407     case NS_FORM_INPUT_FILE:
7408     case NS_FORM_INPUT_TEXT:
7409     case NS_FORM_INPUT_PASSWORD:
7410     case NS_FORM_INPUT_SEARCH:
7411     case NS_FORM_INPUT_TEL:
7412     case NS_FORM_INPUT_EMAIL:
7413     case NS_FORM_INPUT_URL:
7414     case NS_FORM_INPUT_COLOR:
7415       return false;
7416     default:
7417       NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
7418       return false;
7419 #else // DEBUG
7420     default:
7421       return false;
7422 #endif // DEBUG
7423   }
7424 }
7425 
7426 bool
DoesAutocompleteApply() const7427 HTMLInputElement::DoesAutocompleteApply() const
7428 {
7429   switch (mType)
7430   {
7431     case NS_FORM_INPUT_HIDDEN:
7432     case NS_FORM_INPUT_TEXT:
7433     case NS_FORM_INPUT_SEARCH:
7434     case NS_FORM_INPUT_URL:
7435     case NS_FORM_INPUT_TEL:
7436     case NS_FORM_INPUT_EMAIL:
7437     case NS_FORM_INPUT_PASSWORD:
7438     case NS_FORM_INPUT_DATE:
7439     case NS_FORM_INPUT_TIME:
7440     case NS_FORM_INPUT_NUMBER:
7441     case NS_FORM_INPUT_RANGE:
7442     case NS_FORM_INPUT_COLOR:
7443     case NS_FORM_INPUT_MONTH:
7444     case NS_FORM_INPUT_WEEK:
7445     case NS_FORM_INPUT_DATETIME_LOCAL:
7446       return true;
7447 #ifdef DEBUG
7448     case NS_FORM_INPUT_RESET:
7449     case NS_FORM_INPUT_SUBMIT:
7450     case NS_FORM_INPUT_IMAGE:
7451     case NS_FORM_INPUT_BUTTON:
7452     case NS_FORM_INPUT_RADIO:
7453     case NS_FORM_INPUT_CHECKBOX:
7454     case NS_FORM_INPUT_FILE:
7455       return false;
7456     default:
7457       NS_NOTYETIMPLEMENTED("Unexpected input type in DoesAutocompleteApply()");
7458       return false;
7459 #else // DEBUG
7460     default:
7461       return false;
7462 #endif // DEBUG
7463   }
7464 }
7465 
7466 Decimal
GetStep() const7467 HTMLInputElement::GetStep() const
7468 {
7469   MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
7470 
7471   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::step)) {
7472     return GetDefaultStep() * GetStepScaleFactor();
7473   }
7474 
7475   nsAutoString stepStr;
7476   GetAttr(kNameSpaceID_None, nsGkAtoms::step, stepStr);
7477 
7478   if (stepStr.LowerCaseEqualsLiteral("any")) {
7479     // The element can't suffer from step mismatch if there is no step.
7480     return kStepAny;
7481   }
7482 
7483   Decimal step = StringToDecimal(stepStr);
7484   if (!step.isFinite() || step <= Decimal(0)) {
7485     step = GetDefaultStep();
7486   }
7487 
7488   // For input type=date, we round the step value to have a rounded day.
7489   if (mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_MONTH ||
7490       mType == NS_FORM_INPUT_WEEK) {
7491     step = std::max(step.round(), Decimal(1));
7492   }
7493 
7494   return step * GetStepScaleFactor();
7495 }
7496 
7497 // nsIConstraintValidation
7498 
7499 NS_IMETHODIMP
SetCustomValidity(const nsAString & aError)7500 HTMLInputElement::SetCustomValidity(const nsAString& aError)
7501 {
7502   nsIConstraintValidation::SetCustomValidity(aError);
7503 
7504   UpdateState(true);
7505 
7506   return NS_OK;
7507 }
7508 
7509 bool
IsTooLong()7510 HTMLInputElement::IsTooLong()
7511 {
7512   if (!mValueChanged ||
7513       !mLastValueChangeWasInteractive ||
7514       !MinOrMaxLengthApplies() ||
7515       !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength)) {
7516     return false;
7517   }
7518 
7519   int32_t maxLength = MaxLength();
7520 
7521   // Maxlength of -1 means parsing error.
7522   if (maxLength == -1) {
7523     return false;
7524   }
7525 
7526   int32_t textLength = -1;
7527   GetTextLength(&textLength);
7528 
7529   return textLength > maxLength;
7530 }
7531 
7532 bool
IsTooShort()7533 HTMLInputElement::IsTooShort()
7534 {
7535   if (!mValueChanged ||
7536       !mLastValueChangeWasInteractive ||
7537       !MinOrMaxLengthApplies() ||
7538       !HasAttr(kNameSpaceID_None, nsGkAtoms::minlength)) {
7539     return false;
7540   }
7541 
7542   int32_t minLength = MinLength();
7543 
7544   // Minlength of -1 means parsing error.
7545   if (minLength == -1) {
7546     return false;
7547   }
7548 
7549   int32_t textLength = -1;
7550   GetTextLength(&textLength);
7551 
7552   return textLength && textLength < minLength;
7553 }
7554 
7555 bool
IsValueMissing() const7556 HTMLInputElement::IsValueMissing() const
7557 {
7558   // Should use UpdateValueMissingValidityStateForRadio() for type radio.
7559   MOZ_ASSERT(mType != NS_FORM_INPUT_RADIO);
7560 
7561   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::required) ||
7562       !DoesRequiredApply()) {
7563     return false;
7564   }
7565 
7566   if (!IsMutable()) {
7567     return false;
7568   }
7569 
7570   switch (GetValueMode()) {
7571     case VALUE_MODE_VALUE:
7572       return IsValueEmpty();
7573 
7574     case VALUE_MODE_FILENAME:
7575       return GetFilesOrDirectoriesInternal().IsEmpty();
7576 
7577     case VALUE_MODE_DEFAULT_ON:
7578       // This should not be used for type radio.
7579       // See the MOZ_ASSERT at the beginning of the method.
7580       return !mChecked;
7581 
7582     case VALUE_MODE_DEFAULT:
7583     default:
7584       return false;
7585   }
7586 }
7587 
7588 bool
HasTypeMismatch() const7589 HTMLInputElement::HasTypeMismatch() const
7590 {
7591   if (mType != NS_FORM_INPUT_EMAIL && mType != NS_FORM_INPUT_URL) {
7592     return false;
7593   }
7594 
7595   nsAutoString value;
7596   NS_ENSURE_SUCCESS(GetValueInternal(value), false);
7597 
7598   if (value.IsEmpty()) {
7599     return false;
7600   }
7601 
7602   if (mType == NS_FORM_INPUT_EMAIL) {
7603     return HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)
7604              ? !IsValidEmailAddressList(value) : !IsValidEmailAddress(value);
7605   } else if (mType == NS_FORM_INPUT_URL) {
7606     /**
7607      * TODO:
7608      * The URL is not checked as the HTML5 specifications want it to be because
7609      * there is no code to check for a valid URI/IRI according to 3986 and 3987
7610      * RFC's at the moment, see bug 561586.
7611      *
7612      * RFC 3987 (IRI) implementation: bug 42899
7613      *
7614      * HTML5 specifications:
7615      * http://dev.w3.org/html5/spec/infrastructure.html#valid-url
7616      */
7617     nsCOMPtr<nsIIOService> ioService = do_GetIOService();
7618     nsCOMPtr<nsIURI> uri;
7619 
7620     return !NS_SUCCEEDED(ioService->NewURI(NS_ConvertUTF16toUTF8(value), nullptr,
7621                                            nullptr, getter_AddRefs(uri)));
7622   }
7623 
7624   return false;
7625 }
7626 
7627 bool
HasPatternMismatch() const7628 HTMLInputElement::HasPatternMismatch() const
7629 {
7630   if (!DoesPatternApply() ||
7631       !HasAttr(kNameSpaceID_None, nsGkAtoms::pattern)) {
7632     return false;
7633   }
7634 
7635   nsAutoString pattern;
7636   GetAttr(kNameSpaceID_None, nsGkAtoms::pattern, pattern);
7637 
7638   nsAutoString value;
7639   NS_ENSURE_SUCCESS(GetValueInternal(value), false);
7640 
7641   if (value.IsEmpty()) {
7642     return false;
7643   }
7644 
7645   nsIDocument* doc = OwnerDoc();
7646 
7647   return !nsContentUtils::IsPatternMatching(value, pattern, doc);
7648 }
7649 
7650 bool
IsRangeOverflow() const7651 HTMLInputElement::IsRangeOverflow() const
7652 {
7653   // TODO: this is temporary until bug 888331 is fixed.
7654   if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
7655     return false;
7656   }
7657 
7658   Decimal maximum = GetMaximum();
7659   if (maximum.isNaN()) {
7660     return false;
7661   }
7662 
7663   Decimal value = GetValueAsDecimal();
7664   if (value.isNaN()) {
7665     return false;
7666   }
7667 
7668   return value > maximum;
7669 }
7670 
7671 bool
IsRangeUnderflow() const7672 HTMLInputElement::IsRangeUnderflow() const
7673 {
7674   // TODO: this is temporary until bug 888331 is fixed.
7675   if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
7676     return false;
7677   }
7678 
7679   Decimal minimum = GetMinimum();
7680   if (minimum.isNaN()) {
7681     return false;
7682   }
7683 
7684   Decimal value = GetValueAsDecimal();
7685   if (value.isNaN()) {
7686     return false;
7687   }
7688 
7689   return value < minimum;
7690 }
7691 
7692 bool
HasStepMismatch(bool aUseZeroIfValueNaN) const7693 HTMLInputElement::HasStepMismatch(bool aUseZeroIfValueNaN) const
7694 {
7695   if (!DoesStepApply()) {
7696     return false;
7697   }
7698 
7699   Decimal value = GetValueAsDecimal();
7700   if (value.isNaN()) {
7701     if (aUseZeroIfValueNaN) {
7702       value = Decimal(0);
7703     } else {
7704       // The element can't suffer from step mismatch if it's value isn't a number.
7705       return false;
7706     }
7707   }
7708 
7709   Decimal step = GetStep();
7710   if (step == kStepAny) {
7711     return false;
7712   }
7713 
7714   // Value has to be an integral multiple of step.
7715   return NS_floorModulo(value - GetStepBase(), step) != Decimal(0);
7716 }
7717 
7718 /**
7719  * Takes aEmail and attempts to convert everything after the first "@"
7720  * character (if anything) to punycode before returning the complete result via
7721  * the aEncodedEmail out-param. The aIndexOfAt out-param is set to the index of
7722  * the "@" character.
7723  *
7724  * If no "@" is found in aEmail, aEncodedEmail is simply set to aEmail and
7725  * the aIndexOfAt out-param is set to kNotFound.
7726  *
7727  * Returns true in all cases unless an attempt to punycode encode fails. If
7728  * false is returned, aEncodedEmail has not been set.
7729  *
7730  * This function exists because ConvertUTF8toACE() splits on ".", meaning that
7731  * for 'user.name@sld.tld' it would treat "name@sld" as a label. We want to
7732  * encode the domain part only.
7733  */
PunycodeEncodeEmailAddress(const nsAString & aEmail,nsAutoCString & aEncodedEmail,uint32_t * aIndexOfAt)7734 static bool PunycodeEncodeEmailAddress(const nsAString& aEmail,
7735                                        nsAutoCString& aEncodedEmail,
7736                                        uint32_t* aIndexOfAt)
7737 {
7738   nsAutoCString value = NS_ConvertUTF16toUTF8(aEmail);
7739   *aIndexOfAt = (uint32_t)value.FindChar('@');
7740 
7741   if (*aIndexOfAt == (uint32_t)kNotFound ||
7742       *aIndexOfAt == value.Length() - 1) {
7743     aEncodedEmail = value;
7744     return true;
7745   }
7746 
7747   nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
7748   if (!idnSrv) {
7749     NS_ERROR("nsIIDNService isn't present!");
7750     return false;
7751   }
7752 
7753   uint32_t indexOfDomain = *aIndexOfAt + 1;
7754 
7755   const nsDependentCSubstring domain = Substring(value, indexOfDomain);
7756   bool ace;
7757   if (NS_SUCCEEDED(idnSrv->IsACE(domain, &ace)) && !ace) {
7758     nsAutoCString domainACE;
7759     if (NS_FAILED(idnSrv->ConvertUTF8toACE(domain, domainACE))) {
7760       return false;
7761     }
7762     value.Replace(indexOfDomain, domain.Length(), domainACE);
7763   }
7764 
7765   aEncodedEmail = value;
7766   return true;
7767 }
7768 
7769 bool
HasBadInput() const7770 HTMLInputElement::HasBadInput() const
7771 {
7772   if (mType == NS_FORM_INPUT_NUMBER) {
7773     nsAutoString value;
7774     GetValueInternal(value);
7775     if (!value.IsEmpty()) {
7776       // The input can't be bad, otherwise it would have been sanitized to the
7777       // empty string.
7778       NS_ASSERTION(!GetValueAsDecimal().isNaN(), "Should have sanitized");
7779       return false;
7780     }
7781     nsNumberControlFrame* numberControlFrame =
7782       do_QueryFrame(GetPrimaryFrame());
7783     if (numberControlFrame &&
7784         !numberControlFrame->AnonTextControlIsEmpty()) {
7785       // The input the user entered failed to parse as a number.
7786       return true;
7787     }
7788     return false;
7789   }
7790   if (mType == NS_FORM_INPUT_EMAIL) {
7791     // With regards to suffering from bad input the spec says that only the
7792     // punycode conversion works, so we don't care whether the email address is
7793     // valid or not here. (If the email address is invalid then we will be
7794     // suffering from a type mismatch.)
7795     nsAutoString value;
7796     nsAutoCString unused;
7797     uint32_t unused2;
7798     NS_ENSURE_SUCCESS(GetValueInternal(value), false);
7799     HTMLSplitOnSpacesTokenizer tokenizer(value, ',');
7800     while (tokenizer.hasMoreTokens()) {
7801       if (!PunycodeEncodeEmailAddress(tokenizer.nextToken(), unused, &unused2)) {
7802         return true;
7803       }
7804     }
7805     return false;
7806   }
7807   return false;
7808 }
7809 
7810 void
UpdateTooLongValidityState()7811 HTMLInputElement::UpdateTooLongValidityState()
7812 {
7813   SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
7814 }
7815 
7816 void
UpdateTooShortValidityState()7817 HTMLInputElement::UpdateTooShortValidityState()
7818 {
7819   SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort());
7820 }
7821 
7822 void
UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf)7823 HTMLInputElement::UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf)
7824 {
7825   bool notify = mDoneCreating;
7826   nsCOMPtr<nsIDOMHTMLInputElement> selection = GetSelectedRadioButton();
7827 
7828   aIgnoreSelf = aIgnoreSelf || !IsMutable();
7829 
7830   // If there is no selection, that might mean the radio is not in a group.
7831   // In that case, we can look for the checked state of the radio.
7832   bool selected = selection || (!aIgnoreSelf && mChecked);
7833   bool required = !aIgnoreSelf && HasAttr(kNameSpaceID_None, nsGkAtoms::required);
7834   bool valueMissing = false;
7835 
7836   nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
7837 
7838   if (!container) {
7839     SetValidityState(VALIDITY_STATE_VALUE_MISSING,
7840                      IsMutable() && required && !selected);
7841     return;
7842   }
7843 
7844   nsAutoString name;
7845   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
7846 
7847   // If the current radio is required and not ignored, we can assume the entire
7848   // group is required.
7849   if (!required) {
7850     required = (aIgnoreSelf && HasAttr(kNameSpaceID_None, nsGkAtoms::required))
7851                  ? container->GetRequiredRadioCount(name) - 1
7852                  : container->GetRequiredRadioCount(name);
7853   }
7854 
7855   valueMissing = required && !selected;
7856 
7857   if (container->GetValueMissingState(name) != valueMissing) {
7858     container->SetValueMissingState(name, valueMissing);
7859 
7860     SetValidityState(VALIDITY_STATE_VALUE_MISSING, valueMissing);
7861 
7862     // nsRadioSetValueMissingState will call ContentStateChanged while visiting.
7863     nsAutoScriptBlocker scriptBlocker;
7864     nsCOMPtr<nsIRadioVisitor> visitor =
7865       new nsRadioSetValueMissingState(this, valueMissing, notify);
7866     VisitGroup(visitor, notify);
7867   }
7868 }
7869 
7870 void
UpdateValueMissingValidityState()7871 HTMLInputElement::UpdateValueMissingValidityState()
7872 {
7873   if (mType == NS_FORM_INPUT_RADIO) {
7874     UpdateValueMissingValidityStateForRadio(false);
7875     return;
7876   }
7877 
7878   SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
7879 }
7880 
7881 void
UpdateTypeMismatchValidityState()7882 HTMLInputElement::UpdateTypeMismatchValidityState()
7883 {
7884     SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, HasTypeMismatch());
7885 }
7886 
7887 void
UpdatePatternMismatchValidityState()7888 HTMLInputElement::UpdatePatternMismatchValidityState()
7889 {
7890   SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, HasPatternMismatch());
7891 }
7892 
7893 void
UpdateRangeOverflowValidityState()7894 HTMLInputElement::UpdateRangeOverflowValidityState()
7895 {
7896   SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, IsRangeOverflow());
7897 }
7898 
7899 void
UpdateRangeUnderflowValidityState()7900 HTMLInputElement::UpdateRangeUnderflowValidityState()
7901 {
7902   SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, IsRangeUnderflow());
7903 }
7904 
7905 void
UpdateStepMismatchValidityState()7906 HTMLInputElement::UpdateStepMismatchValidityState()
7907 {
7908   SetValidityState(VALIDITY_STATE_STEP_MISMATCH, HasStepMismatch());
7909 }
7910 
7911 void
UpdateBadInputValidityState()7912 HTMLInputElement::UpdateBadInputValidityState()
7913 {
7914   SetValidityState(VALIDITY_STATE_BAD_INPUT, HasBadInput());
7915 }
7916 
7917 void
UpdateAllValidityStates(bool aNotify)7918 HTMLInputElement::UpdateAllValidityStates(bool aNotify)
7919 {
7920   bool validBefore = IsValid();
7921   UpdateTooLongValidityState();
7922   UpdateTooShortValidityState();
7923   UpdateValueMissingValidityState();
7924   UpdateTypeMismatchValidityState();
7925   UpdatePatternMismatchValidityState();
7926   UpdateRangeOverflowValidityState();
7927   UpdateRangeUnderflowValidityState();
7928   UpdateStepMismatchValidityState();
7929   UpdateBadInputValidityState();
7930 
7931   if (validBefore != IsValid()) {
7932     UpdateState(aNotify);
7933   }
7934 }
7935 
7936 void
UpdateBarredFromConstraintValidation()7937 HTMLInputElement::UpdateBarredFromConstraintValidation()
7938 {
7939   SetBarredFromConstraintValidation(mType == NS_FORM_INPUT_HIDDEN ||
7940                                     mType == NS_FORM_INPUT_BUTTON ||
7941                                     mType == NS_FORM_INPUT_RESET ||
7942                                     HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) ||
7943                                     IsDisabled());
7944 }
7945 
7946 void
GetValidationMessage(nsAString & aValidationMessage,ErrorResult & aRv)7947 HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
7948                                        ErrorResult& aRv)
7949 {
7950   aRv = GetValidationMessage(aValidationMessage);
7951 }
7952 
7953 nsresult
GetValidationMessage(nsAString & aValidationMessage,ValidityStateType aType)7954 HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
7955                                        ValidityStateType aType)
7956 {
7957   nsresult rv = NS_OK;
7958 
7959   switch (aType)
7960   {
7961     case VALIDITY_STATE_TOO_LONG:
7962     {
7963       nsXPIDLString message;
7964       int32_t maxLength = MaxLength();
7965       int32_t textLength = -1;
7966       nsAutoString strMaxLength;
7967       nsAutoString strTextLength;
7968 
7969       GetTextLength(&textLength);
7970 
7971       strMaxLength.AppendInt(maxLength);
7972       strTextLength.AppendInt(textLength);
7973 
7974       const char16_t* params[] = { strMaxLength.get(), strTextLength.get() };
7975       rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
7976                                                  "FormValidationTextTooLong",
7977                                                  params, message);
7978       aValidationMessage = message;
7979       break;
7980     }
7981     case VALIDITY_STATE_TOO_SHORT:
7982     {
7983       nsXPIDLString message;
7984       int32_t minLength = MinLength();
7985       int32_t textLength = -1;
7986       nsAutoString strMinLength;
7987       nsAutoString strTextLength;
7988 
7989       GetTextLength(&textLength);
7990 
7991       strMinLength.AppendInt(minLength);
7992       strTextLength.AppendInt(textLength);
7993 
7994       const char16_t* params[] = { strMinLength.get(), strTextLength.get() };
7995       rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
7996                                                  "FormValidationTextTooShort",
7997                                                  params, message);
7998       aValidationMessage = message;
7999       break;
8000     }
8001     case VALIDITY_STATE_VALUE_MISSING:
8002     {
8003       nsXPIDLString message;
8004       nsAutoCString key;
8005       switch (mType)
8006       {
8007         case NS_FORM_INPUT_FILE:
8008           key.AssignLiteral("FormValidationFileMissing");
8009           break;
8010         case NS_FORM_INPUT_CHECKBOX:
8011           key.AssignLiteral("FormValidationCheckboxMissing");
8012           break;
8013         case NS_FORM_INPUT_RADIO:
8014           key.AssignLiteral("FormValidationRadioMissing");
8015           break;
8016         case NS_FORM_INPUT_NUMBER:
8017           key.AssignLiteral("FormValidationBadInputNumber");
8018           break;
8019         default:
8020           key.AssignLiteral("FormValidationValueMissing");
8021       }
8022       rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8023                                               key.get(), message);
8024       aValidationMessage = message;
8025       break;
8026     }
8027     case VALIDITY_STATE_TYPE_MISMATCH:
8028     {
8029       nsXPIDLString message;
8030       nsAutoCString key;
8031       if (mType == NS_FORM_INPUT_EMAIL) {
8032         key.AssignLiteral("FormValidationInvalidEmail");
8033       } else if (mType == NS_FORM_INPUT_URL) {
8034         key.AssignLiteral("FormValidationInvalidURL");
8035       } else {
8036         return NS_ERROR_UNEXPECTED;
8037       }
8038       rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8039                                               key.get(), message);
8040       aValidationMessage = message;
8041       break;
8042     }
8043     case VALIDITY_STATE_PATTERN_MISMATCH:
8044     {
8045       nsXPIDLString message;
8046       nsAutoString title;
8047       GetAttr(kNameSpaceID_None, nsGkAtoms::title, title);
8048       if (title.IsEmpty()) {
8049         rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8050                                                 "FormValidationPatternMismatch",
8051                                                 message);
8052       } else {
8053         if (title.Length() > nsIConstraintValidation::sContentSpecifiedMaxLengthMessage) {
8054           title.Truncate(nsIConstraintValidation::sContentSpecifiedMaxLengthMessage);
8055         }
8056         const char16_t* params[] = { title.get() };
8057         rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8058                                                    "FormValidationPatternMismatchWithTitle",
8059                                                    params, message);
8060       }
8061       aValidationMessage = message;
8062       break;
8063     }
8064     case VALIDITY_STATE_RANGE_OVERFLOW:
8065     {
8066       static const char kNumberOverTemplate[] = "FormValidationNumberRangeOverflow";
8067       static const char kDateOverTemplate[] = "FormValidationDateRangeOverflow";
8068       static const char kTimeOverTemplate[] = "FormValidationTimeRangeOverflow";
8069 
8070       const char* msgTemplate;
8071       nsXPIDLString message;
8072 
8073       nsAutoString maxStr;
8074       if (mType == NS_FORM_INPUT_NUMBER ||
8075           mType == NS_FORM_INPUT_RANGE) {
8076         msgTemplate = kNumberOverTemplate;
8077 
8078         //We want to show the value as parsed when it's a number
8079         Decimal maximum = GetMaximum();
8080         MOZ_ASSERT(!maximum.isNaN());
8081 
8082         char buf[32];
8083         DebugOnly<bool> ok = maximum.toString(buf, ArrayLength(buf));
8084         maxStr.AssignASCII(buf);
8085         MOZ_ASSERT(ok, "buf not big enough");
8086       } else if (IsDateTimeInputType(mType)) {
8087         msgTemplate = mType == NS_FORM_INPUT_TIME ? kTimeOverTemplate : kDateOverTemplate;
8088         GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
8089       } else {
8090         msgTemplate = kNumberOverTemplate;
8091         NS_NOTREACHED("Unexpected input type");
8092       }
8093 
8094       const char16_t* params[] = { maxStr.get() };
8095       rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8096                                                  msgTemplate,
8097                                                  params, message);
8098       aValidationMessage = message;
8099       break;
8100     }
8101     case VALIDITY_STATE_RANGE_UNDERFLOW:
8102     {
8103       static const char kNumberUnderTemplate[] = "FormValidationNumberRangeUnderflow";
8104       static const char kDateUnderTemplate[] = "FormValidationDateRangeUnderflow";
8105       static const char kTimeUnderTemplate[] = "FormValidationTimeRangeUnderflow";
8106 
8107       const char* msgTemplate;
8108       nsXPIDLString message;
8109 
8110       nsAutoString minStr;
8111       if (mType == NS_FORM_INPUT_NUMBER ||
8112           mType == NS_FORM_INPUT_RANGE) {
8113         msgTemplate = kNumberUnderTemplate;
8114 
8115         Decimal minimum = GetMinimum();
8116         MOZ_ASSERT(!minimum.isNaN());
8117 
8118         char buf[32];
8119         DebugOnly<bool> ok = minimum.toString(buf, ArrayLength(buf));
8120         minStr.AssignASCII(buf);
8121         MOZ_ASSERT(ok, "buf not big enough");
8122       } else if (IsDateTimeInputType(mType)) {
8123         msgTemplate = mType == NS_FORM_INPUT_TIME ? kTimeUnderTemplate : kDateUnderTemplate;
8124         GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
8125       } else {
8126         msgTemplate = kNumberUnderTemplate;
8127         NS_NOTREACHED("Unexpected input type");
8128       }
8129 
8130       const char16_t* params[] = { minStr.get() };
8131       rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8132                                                  msgTemplate,
8133                                                  params, message);
8134       aValidationMessage = message;
8135       break;
8136     }
8137     case VALIDITY_STATE_STEP_MISMATCH:
8138     {
8139       nsXPIDLString message;
8140 
8141       Decimal value = GetValueAsDecimal();
8142       MOZ_ASSERT(!value.isNaN());
8143 
8144       Decimal step = GetStep();
8145       MOZ_ASSERT(step != kStepAny && step > Decimal(0));
8146 
8147       Decimal stepBase = GetStepBase();
8148 
8149       Decimal valueLow = value - NS_floorModulo(value - stepBase, step);
8150       Decimal valueHigh = value + step - NS_floorModulo(value - stepBase, step);
8151 
8152       Decimal maximum = GetMaximum();
8153 
8154       if (maximum.isNaN() || valueHigh <= maximum) {
8155         nsAutoString valueLowStr, valueHighStr;
8156         ConvertNumberToString(valueLow, valueLowStr);
8157         ConvertNumberToString(valueHigh, valueHighStr);
8158 
8159         if (valueLowStr.Equals(valueHighStr)) {
8160           const char16_t* params[] = { valueLowStr.get() };
8161           rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8162                                                      "FormValidationStepMismatchOneValue",
8163                                                      params, message);
8164         } else {
8165           const char16_t* params[] = { valueLowStr.get(), valueHighStr.get() };
8166           rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8167                                                      "FormValidationStepMismatch",
8168                                                      params, message);
8169         }
8170       } else {
8171         nsAutoString valueLowStr;
8172         ConvertNumberToString(valueLow, valueLowStr);
8173 
8174         const char16_t* params[] = { valueLowStr.get() };
8175         rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8176                                                    "FormValidationStepMismatchOneValue",
8177                                                    params, message);
8178       }
8179 
8180       aValidationMessage = message;
8181       break;
8182     }
8183     case VALIDITY_STATE_BAD_INPUT:
8184     {
8185       nsXPIDLString message;
8186       nsAutoCString key;
8187       if (mType == NS_FORM_INPUT_NUMBER) {
8188         key.AssignLiteral("FormValidationBadInputNumber");
8189       } else if (mType == NS_FORM_INPUT_EMAIL) {
8190         key.AssignLiteral("FormValidationInvalidEmail");
8191       } else {
8192         return NS_ERROR_UNEXPECTED;
8193       }
8194       rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8195                                               key.get(), message);
8196       aValidationMessage = message;
8197       break;
8198     }
8199     default:
8200       rv = nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
8201   }
8202 
8203   return rv;
8204 }
8205 
8206 //static
8207 bool
IsValidEmailAddressList(const nsAString & aValue)8208 HTMLInputElement::IsValidEmailAddressList(const nsAString& aValue)
8209 {
8210   HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
8211 
8212   while (tokenizer.hasMoreTokens()) {
8213     if (!IsValidEmailAddress(tokenizer.nextToken())) {
8214       return false;
8215     }
8216   }
8217 
8218   return !tokenizer.separatorAfterCurrentToken();
8219 }
8220 
8221 //static
8222 bool
IsValidEmailAddress(const nsAString & aValue)8223 HTMLInputElement::IsValidEmailAddress(const nsAString& aValue)
8224 {
8225   // Email addresses can't be empty and can't end with a '.' or '-'.
8226   if (aValue.IsEmpty() || aValue.Last() == '.' || aValue.Last() == '-') {
8227     return false;
8228   }
8229 
8230   uint32_t atPos;
8231   nsAutoCString value;
8232   if (!PunycodeEncodeEmailAddress(aValue, value, &atPos) ||
8233       atPos == (uint32_t)kNotFound || atPos == 0 || atPos == value.Length() - 1) {
8234     // Could not encode, or "@" was not found, or it was at the start or end
8235     // of the input - in all cases, not a valid email address.
8236     return false;
8237   }
8238 
8239   uint32_t length = value.Length();
8240   uint32_t i = 0;
8241 
8242   // Parsing the username.
8243   for (; i < atPos; ++i) {
8244     char16_t c = value[i];
8245 
8246     // The username characters have to be in this list to be valid.
8247     if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
8248           c == '.' || c == '!' || c == '#' || c == '$' || c == '%' ||
8249           c == '&' || c == '\''|| c == '*' || c == '+' || c == '-' ||
8250           c == '/' || c == '=' || c == '?' || c == '^' || c == '_' ||
8251           c == '`' || c == '{' || c == '|' || c == '}' || c == '~' )) {
8252       return false;
8253     }
8254   }
8255 
8256   // Skip the '@'.
8257   ++i;
8258 
8259   // The domain name can't begin with a dot or a dash.
8260   if (value[i] == '.' || value[i] == '-') {
8261     return false;
8262   }
8263 
8264   // Parsing the domain name.
8265   for (; i < length; ++i) {
8266     char16_t c = value[i];
8267 
8268     if (c == '.') {
8269       // A dot can't follow a dot or a dash.
8270       if (value[i-1] == '.' || value[i-1] == '-') {
8271         return false;
8272       }
8273     } else if (c == '-'){
8274       // A dash can't follow a dot.
8275       if (value[i-1] == '.') {
8276         return false;
8277       }
8278     } else if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
8279                  c == '-')) {
8280       // The domain characters have to be in this list to be valid.
8281       return false;
8282     }
8283   }
8284 
8285   return true;
8286 }
8287 
NS_IMETHODIMP_(bool)8288 NS_IMETHODIMP_(bool)
8289 HTMLInputElement::IsSingleLineTextControl() const
8290 {
8291   return IsSingleLineTextControl(false);
8292 }
8293 
NS_IMETHODIMP_(bool)8294 NS_IMETHODIMP_(bool)
8295 HTMLInputElement::IsTextArea() const
8296 {
8297   return false;
8298 }
8299 
NS_IMETHODIMP_(bool)8300 NS_IMETHODIMP_(bool)
8301 HTMLInputElement::IsPlainTextControl() const
8302 {
8303   // need to check our HTML attribute and/or CSS.
8304   return true;
8305 }
8306 
NS_IMETHODIMP_(bool)8307 NS_IMETHODIMP_(bool)
8308 HTMLInputElement::IsPasswordTextControl() const
8309 {
8310   return mType == NS_FORM_INPUT_PASSWORD;
8311 }
8312 
NS_IMETHODIMP_(int32_t)8313 NS_IMETHODIMP_(int32_t)
8314 HTMLInputElement::GetCols()
8315 {
8316   // Else we know (assume) it is an input with size attr
8317   const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::size);
8318   if (attr && attr->Type() == nsAttrValue::eInteger) {
8319     int32_t cols = attr->GetIntegerValue();
8320     if (cols > 0) {
8321       return cols;
8322     }
8323   }
8324 
8325   return DEFAULT_COLS;
8326 }
8327 
NS_IMETHODIMP_(int32_t)8328 NS_IMETHODIMP_(int32_t)
8329 HTMLInputElement::GetWrapCols()
8330 {
8331   return 0; // only textarea's can have wrap cols
8332 }
8333 
NS_IMETHODIMP_(int32_t)8334 NS_IMETHODIMP_(int32_t)
8335 HTMLInputElement::GetRows()
8336 {
8337   return DEFAULT_ROWS;
8338 }
8339 
NS_IMETHODIMP_(void)8340 NS_IMETHODIMP_(void)
8341 HTMLInputElement::GetDefaultValueFromContent(nsAString& aValue)
8342 {
8343   nsTextEditorState *state = GetEditorState();
8344   if (state) {
8345     GetDefaultValue(aValue);
8346     // This is called by the frame to show the value.
8347     // We have to sanitize it when needed.
8348     if (mDoneCreating) {
8349       SanitizeValue(aValue);
8350     }
8351   }
8352 }
8353 
NS_IMETHODIMP_(bool)8354 NS_IMETHODIMP_(bool)
8355 HTMLInputElement::ValueChanged() const
8356 {
8357   return mValueChanged;
8358 }
8359 
NS_IMETHODIMP_(void)8360 NS_IMETHODIMP_(void)
8361 HTMLInputElement::GetTextEditorValue(nsAString& aValue,
8362                                      bool aIgnoreWrap) const
8363 {
8364   nsTextEditorState* state = GetEditorState();
8365   if (state) {
8366     state->GetValue(aValue, aIgnoreWrap);
8367   }
8368 }
8369 
NS_IMETHODIMP_(void)8370 NS_IMETHODIMP_(void)
8371 HTMLInputElement::InitializeKeyboardEventListeners()
8372 {
8373   nsTextEditorState* state = GetEditorState();
8374   if (state) {
8375     state->InitializeKeyboardEventListeners();
8376   }
8377 }
8378 
NS_IMETHODIMP_(void)8379 NS_IMETHODIMP_(void)
8380 HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
8381 {
8382   mLastValueChangeWasInteractive = aWasInteractiveUserChange;
8383 
8384   UpdateAllValidityStates(aNotify);
8385 
8386   if (HasDirAuto()) {
8387     SetDirectionIfAuto(true, aNotify);
8388   }
8389 
8390   // :placeholder-shown pseudo-class may change when the value changes.
8391   // However, we don't want to waste cycles if the state doesn't apply.
8392   if (PlaceholderApplies() &&
8393       HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
8394     UpdateState(aNotify);
8395   }
8396 }
8397 
NS_IMETHODIMP_(bool)8398 NS_IMETHODIMP_(bool)
8399 HTMLInputElement::HasCachedSelection()
8400 {
8401   bool isCached = false;
8402   nsTextEditorState* state = GetEditorState();
8403   if (state) {
8404     isCached = state->IsSelectionCached() &&
8405                state->HasNeverInitializedBefore() &&
8406                !state->GetSelectionProperties().IsDefault();
8407     if (isCached) {
8408       state->WillInitEagerly();
8409     }
8410   }
8411   return isCached;
8412 }
8413 
8414 void
FieldSetDisabledChanged(bool aNotify)8415 HTMLInputElement::FieldSetDisabledChanged(bool aNotify)
8416 {
8417   UpdateValueMissingValidityState();
8418   UpdateBarredFromConstraintValidation();
8419 
8420   nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
8421 }
8422 
8423 void
SetFilePickerFiltersFromAccept(nsIFilePicker * filePicker)8424 HTMLInputElement::SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker)
8425 {
8426   // We always add |filterAll|
8427   filePicker->AppendFilters(nsIFilePicker::filterAll);
8428 
8429   NS_ASSERTION(HasAttr(kNameSpaceID_None, nsGkAtoms::accept),
8430                "You should not call SetFilePickerFiltersFromAccept if the"
8431                " element has no accept attribute!");
8432 
8433   // Services to retrieve image/*, audio/*, video/* filters
8434   nsCOMPtr<nsIStringBundleService> stringService =
8435     mozilla::services::GetStringBundleService();
8436   if (!stringService) {
8437     return;
8438   }
8439   nsCOMPtr<nsIStringBundle> filterBundle;
8440   if (NS_FAILED(stringService->CreateBundle("chrome://global/content/filepicker.properties",
8441                                             getter_AddRefs(filterBundle)))) {
8442     return;
8443   }
8444 
8445   // Service to retrieve mime type information for mime types filters
8446   nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
8447   if (!mimeService) {
8448     return;
8449   }
8450 
8451   nsAutoString accept;
8452   GetAttr(kNameSpaceID_None, nsGkAtoms::accept, accept);
8453 
8454   HTMLSplitOnSpacesTokenizer tokenizer(accept, ',');
8455 
8456   nsTArray<nsFilePickerFilter> filters;
8457   nsString allExtensionsList;
8458 
8459   bool allMimeTypeFiltersAreValid = true;
8460   bool atLeastOneFileExtensionFilter = false;
8461 
8462   // Retrieve all filters
8463   while (tokenizer.hasMoreTokens()) {
8464     const nsDependentSubstring& token = tokenizer.nextToken();
8465 
8466     if (token.IsEmpty()) {
8467       continue;
8468     }
8469 
8470     int32_t filterMask = 0;
8471     nsString filterName;
8472     nsString extensionListStr;
8473 
8474     // First, check for image/audio/video filters...
8475     if (token.EqualsLiteral("image/*")) {
8476       filterMask = nsIFilePicker::filterImages;
8477       filterBundle->GetStringFromName(u"imageFilter",
8478                                       getter_Copies(extensionListStr));
8479     } else if (token.EqualsLiteral("audio/*")) {
8480       filterMask = nsIFilePicker::filterAudio;
8481       filterBundle->GetStringFromName(u"audioFilter",
8482                                       getter_Copies(extensionListStr));
8483     } else if (token.EqualsLiteral("video/*")) {
8484       filterMask = nsIFilePicker::filterVideo;
8485       filterBundle->GetStringFromName(u"videoFilter",
8486                                       getter_Copies(extensionListStr));
8487     } else if (token.First() == '.') {
8488       if (token.Contains(';') || token.Contains('*')) {
8489         // Ignore this filter as it contains reserved characters
8490         continue;
8491       }
8492       extensionListStr = NS_LITERAL_STRING("*") + token;
8493       filterName = extensionListStr;
8494       atLeastOneFileExtensionFilter = true;
8495     } else {
8496       //... if no image/audio/video filter is found, check mime types filters
8497       nsCOMPtr<nsIMIMEInfo> mimeInfo;
8498       if (NS_FAILED(mimeService->GetFromTypeAndExtension(
8499                       NS_ConvertUTF16toUTF8(token),
8500                       EmptyCString(), // No extension
8501                       getter_AddRefs(mimeInfo))) ||
8502           !mimeInfo) {
8503         allMimeTypeFiltersAreValid =  false;
8504         continue;
8505       }
8506 
8507       // Get a name for the filter: first try the description, then the mime type
8508       // name if there is no description
8509       mimeInfo->GetDescription(filterName);
8510       if (filterName.IsEmpty()) {
8511         nsCString mimeTypeName;
8512         mimeInfo->GetType(mimeTypeName);
8513         CopyUTF8toUTF16(mimeTypeName, filterName);
8514       }
8515 
8516       // Get extension list
8517       nsCOMPtr<nsIUTF8StringEnumerator> extensions;
8518       mimeInfo->GetFileExtensions(getter_AddRefs(extensions));
8519 
8520       bool hasMore;
8521       while (NS_SUCCEEDED(extensions->HasMore(&hasMore)) && hasMore) {
8522         nsCString extension;
8523         if (NS_FAILED(extensions->GetNext(extension))) {
8524           continue;
8525         }
8526         if (!extensionListStr.IsEmpty()) {
8527           extensionListStr.AppendLiteral("; ");
8528         }
8529         extensionListStr += NS_LITERAL_STRING("*.") +
8530                             NS_ConvertUTF8toUTF16(extension);
8531       }
8532     }
8533 
8534     if (!filterMask && (extensionListStr.IsEmpty() || filterName.IsEmpty())) {
8535       // No valid filter found
8536       allMimeTypeFiltersAreValid = false;
8537       continue;
8538     }
8539 
8540     // If we arrived here, that means we have a valid filter: let's create it
8541     // and add it to our list, if no similar filter is already present
8542     nsFilePickerFilter filter;
8543     if (filterMask) {
8544       filter = nsFilePickerFilter(filterMask);
8545     } else {
8546       filter = nsFilePickerFilter(filterName, extensionListStr);
8547     }
8548 
8549     if (!filters.Contains(filter)) {
8550       if (!allExtensionsList.IsEmpty()) {
8551         allExtensionsList.AppendLiteral("; ");
8552       }
8553       allExtensionsList += extensionListStr;
8554       filters.AppendElement(filter);
8555     }
8556   }
8557 
8558   // Remove similar filters
8559   // Iterate over a copy, as we might modify the original filters list
8560   nsTArray<nsFilePickerFilter> filtersCopy;
8561   filtersCopy = filters;
8562   for (uint32_t i = 0; i < filtersCopy.Length(); ++i) {
8563     const nsFilePickerFilter& filterToCheck = filtersCopy[i];
8564     if (filterToCheck.mFilterMask) {
8565       continue;
8566     }
8567     for (uint32_t j = 0; j < filtersCopy.Length(); ++j) {
8568       if (i == j) {
8569         continue;
8570       }
8571       // Check if this filter's extension list is a substring of the other one.
8572       // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
8573       // be removed.
8574       // Add an extra "; " to be sure the check will work and avoid cases like
8575       // "*.xls" being a subtring of "*.xslx" while those are two differents
8576       // filters and none should be removed.
8577       if (FindInReadable(filterToCheck.mFilter + NS_LITERAL_STRING(";"),
8578                          filtersCopy[j].mFilter + NS_LITERAL_STRING(";"))) {
8579         // We already have a similar, less restrictive filter (i.e.
8580         // filterToCheck extensionList is just a subset of another filter
8581         // extension list): remove this one
8582         filters.RemoveElement(filterToCheck);
8583       }
8584     }
8585   }
8586 
8587   // Add "All Supported Types" filter
8588   if (filters.Length() > 1) {
8589     nsXPIDLString title;
8590     nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
8591                                        "AllSupportedTypes", title);
8592     filePicker->AppendFilter(title, allExtensionsList);
8593   }
8594 
8595   // Add each filter
8596   for (uint32_t i = 0; i < filters.Length(); ++i) {
8597     const nsFilePickerFilter& filter = filters[i];
8598     if (filter.mFilterMask) {
8599       filePicker->AppendFilters(filter.mFilterMask);
8600     } else {
8601       filePicker->AppendFilter(filter.mTitle, filter.mFilter);
8602     }
8603   }
8604 
8605   if (filters.Length() >= 1 &&
8606       (allMimeTypeFiltersAreValid || atLeastOneFileExtensionFilter)) {
8607     // |filterAll| will always use index=0 so we need to set index=1 as the
8608     // current filter.
8609     filePicker->SetFilterIndex(1);
8610   }
8611 }
8612 
8613 Decimal
GetStepScaleFactor() const8614 HTMLInputElement::GetStepScaleFactor() const
8615 {
8616   MOZ_ASSERT(DoesStepApply());
8617 
8618   switch (mType) {
8619     case NS_FORM_INPUT_DATE:
8620       return kStepScaleFactorDate;
8621     case NS_FORM_INPUT_NUMBER:
8622     case NS_FORM_INPUT_RANGE:
8623       return kStepScaleFactorNumberRange;
8624     case NS_FORM_INPUT_TIME:
8625       return kStepScaleFactorTime;
8626     case NS_FORM_INPUT_MONTH:
8627       return kStepScaleFactorMonth;
8628     case NS_FORM_INPUT_WEEK:
8629       return kStepScaleFactorWeek;
8630     default:
8631       MOZ_ASSERT(false, "Unrecognized input type");
8632       return Decimal::nan();
8633   }
8634 }
8635 
8636 Decimal
GetDefaultStep() const8637 HTMLInputElement::GetDefaultStep() const
8638 {
8639   MOZ_ASSERT(DoesStepApply());
8640 
8641   switch (mType) {
8642     case NS_FORM_INPUT_DATE:
8643     case NS_FORM_INPUT_MONTH:
8644     case NS_FORM_INPUT_WEEK:
8645     case NS_FORM_INPUT_NUMBER:
8646     case NS_FORM_INPUT_RANGE:
8647       return kDefaultStep;
8648     case NS_FORM_INPUT_TIME:
8649       return kDefaultStepTime;
8650     default:
8651       MOZ_ASSERT(false, "Unrecognized input type");
8652       return Decimal::nan();
8653   }
8654 }
8655 
8656 void
UpdateValidityUIBits(bool aIsFocused)8657 HTMLInputElement::UpdateValidityUIBits(bool aIsFocused)
8658 {
8659   if (aIsFocused) {
8660     // If the invalid UI is shown, we should show it while focusing (and
8661     // update). Otherwise, we should not.
8662     mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
8663 
8664     // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
8665     // UI while typing.
8666     mCanShowValidUI = ShouldShowValidityUI();
8667   } else {
8668     mCanShowInvalidUI = true;
8669     mCanShowValidUI = true;
8670   }
8671 }
8672 
8673 void
UpdateHasRange()8674 HTMLInputElement::UpdateHasRange()
8675 {
8676   /*
8677    * There is a range if min/max applies for the type and if the element
8678    * currently have a valid min or max.
8679    */
8680 
8681   mHasRange = false;
8682 
8683   // TODO: this is temporary until bug 888331 is fixed.
8684   if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
8685     return;
8686   }
8687 
8688   Decimal minimum = GetMinimum();
8689   if (!minimum.isNaN()) {
8690     mHasRange = true;
8691     return;
8692   }
8693 
8694   Decimal maximum = GetMaximum();
8695   if (!maximum.isNaN()) {
8696     mHasRange = true;
8697     return;
8698   }
8699 }
8700 
8701 void
PickerClosed()8702 HTMLInputElement::PickerClosed()
8703 {
8704   mPickerRunning = false;
8705 }
8706 
8707 JSObject*
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)8708 HTMLInputElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
8709 {
8710   return HTMLInputElementBinding::Wrap(aCx, this, aGivenProto);
8711 }
8712 
8713 void
ClearGetFilesHelpers()8714 HTMLInputElement::ClearGetFilesHelpers()
8715 {
8716   if (mGetFilesRecursiveHelper) {
8717     mGetFilesRecursiveHelper->Unlink();
8718     mGetFilesRecursiveHelper = nullptr;
8719   }
8720 
8721   if (mGetFilesNonRecursiveHelper) {
8722     mGetFilesNonRecursiveHelper->Unlink();
8723     mGetFilesNonRecursiveHelper = nullptr;
8724   }
8725 }
8726 
8727 GetFilesHelper*
GetOrCreateGetFilesHelper(bool aRecursiveFlag,ErrorResult & aRv)8728 HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag,
8729                                             ErrorResult& aRv)
8730 {
8731   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
8732   MOZ_ASSERT(global);
8733   if (!global) {
8734     aRv.Throw(NS_ERROR_FAILURE);
8735     return nullptr;
8736   }
8737 
8738   if (aRecursiveFlag) {
8739     if (!mGetFilesRecursiveHelper) {
8740       mGetFilesRecursiveHelper =
8741        GetFilesHelper::Create(global,
8742                               GetFilesOrDirectoriesInternal(),
8743                               aRecursiveFlag, aRv);
8744       if (NS_WARN_IF(aRv.Failed())) {
8745         return nullptr;
8746       }
8747     }
8748 
8749     return mGetFilesRecursiveHelper;
8750   }
8751 
8752   if (!mGetFilesNonRecursiveHelper) {
8753     mGetFilesNonRecursiveHelper =
8754      GetFilesHelper::Create(global,
8755                             GetFilesOrDirectoriesInternal(),
8756                             aRecursiveFlag, aRv);
8757     if (NS_WARN_IF(aRv.Failed())) {
8758       return nullptr;
8759     }
8760   }
8761 
8762   return mGetFilesNonRecursiveHelper;
8763 }
8764 
8765 void
UpdateEntries(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories)8766 HTMLInputElement::UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
8767 {
8768   MOZ_ASSERT(mEntries.IsEmpty());
8769 
8770   nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
8771   MOZ_ASSERT(global);
8772 
8773   RefPtr<FileSystem> fs = FileSystem::Create(global);
8774   if (NS_WARN_IF(!fs)) {
8775     return;
8776   }
8777 
8778   Sequence<RefPtr<FileSystemEntry>> entries;
8779   for (uint32_t i = 0; i < aFilesOrDirectories.Length(); ++i) {
8780     RefPtr<FileSystemEntry> entry =
8781       FileSystemEntry::Create(global, aFilesOrDirectories[i], fs);
8782     MOZ_ASSERT(entry);
8783 
8784     if (!entries.AppendElement(entry, fallible)) {
8785       return;
8786     }
8787   }
8788 
8789   // The root fileSystem is a DirectoryEntry object that contains only the
8790   // dropped fileEntry and directoryEntry objects.
8791   fs->CreateRoot(entries);
8792 
8793   mEntries.SwapElements(entries);
8794 }
8795 
8796 void
GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>> & aSequence)8797 HTMLInputElement::GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>>& aSequence)
8798 {
8799   Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED, true);
8800   aSequence.AppendElements(mEntries);
8801 }
8802 
8803 } // namespace dom
8804 } // namespace mozilla
8805 
8806 #undef NS_ORIGINAL_CHECKED_VALUE
8807