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