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