HTMLDialogElement.cpp (28984B)
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/HTMLDialogElement.h" 8 9 #include "mozilla/dom/BindContext.h" 10 #include "mozilla/dom/CloseWatcher.h" 11 #include "mozilla/dom/CloseWatcherManager.h" 12 #include "mozilla/dom/ElementBinding.h" 13 #include "mozilla/dom/HTMLButtonElement.h" 14 #include "mozilla/dom/HTMLDialogElementBinding.h" 15 #include "mozilla/dom/UnbindContext.h" 16 #include "nsContentUtils.h" 17 #include "nsFocusManager.h" 18 #include "nsIDOMEventListener.h" 19 #include "nsIFrame.h" 20 21 NS_IMPL_NS_NEW_HTML_ELEMENT(Dialog) 22 23 namespace mozilla::dom { 24 25 static constexpr nsAttrValue::EnumTableEntry kClosedbyTable[] = { 26 {"", HTMLDialogElement::ClosedBy::Auto}, 27 {"none", HTMLDialogElement::ClosedBy::None}, 28 {"any", HTMLDialogElement::ClosedBy::Any}, 29 {"closerequest", HTMLDialogElement::ClosedBy::CloseRequest}, 30 }; 31 32 static constexpr const nsAttrValue::EnumTableEntry* kClosedbyAuto = 33 &kClosedbyTable[0]; 34 static constexpr const nsAttrValue::EnumTableEntry* kClosedbyDefault = 35 &kClosedbyTable[1]; 36 static constexpr const nsAttrValue::EnumTableEntry* kClosedbyModalDefault = 37 &kClosedbyTable[3]; 38 39 HTMLDialogElement::~HTMLDialogElement() = default; 40 41 NS_IMPL_ELEMENT_CLONE(HTMLDialogElement) 42 43 class DialogCloseWatcherListener : public nsIDOMEventListener { 44 public: 45 NS_DECL_ISUPPORTS 46 47 explicit DialogCloseWatcherListener(HTMLDialogElement* aDialog) { 48 mDialog = do_GetWeakReference(aDialog); 49 } 50 51 // https://html.spec.whatwg.org/#set-the-dialog-close-watcher 52 NS_IMETHODIMP HandleEvent(Event* aEvent) override { 53 RefPtr<nsINode> node = do_QueryReferent(mDialog); 54 if (HTMLDialogElement* dialog = HTMLDialogElement::FromNodeOrNull(node)) { 55 nsAutoString eventType; 56 aEvent->GetType(eventType); 57 if (eventType.EqualsLiteral("cancel")) { 58 // 3. - cancelAction given canPreventClose being to return the result of 59 // firing an event named cancel at dialog, with the cancelable attribute 60 // initialized to canPreventClose. 61 bool defaultAction = true; 62 auto cancelable = 63 aEvent->Cancelable() ? Cancelable::eYes : Cancelable::eNo; 64 nsContentUtils::DispatchTrustedEvent(dialog->OwnerDoc(), dialog, 65 u"cancel"_ns, CanBubble::eNo, 66 cancelable, &defaultAction); 67 if (!defaultAction) { 68 aEvent->PreventDefault(); 69 } 70 } else if (eventType.EqualsLiteral("close")) { 71 // 3. - closeAction being to close the dialog given dialog, dialog's 72 // request close return value, and dialog's request close source 73 // element. 74 Optional<nsAString> retValue; 75 dialog->GetRequestCloseReturnValue(retValue); 76 RefPtr<Element> source = dialog->GetRequestCloseSourceElement(); 77 dialog->Close(source, retValue); 78 } 79 } 80 return NS_OK; 81 } 82 83 private: 84 virtual ~DialogCloseWatcherListener() = default; 85 nsWeakPtr mDialog; 86 }; 87 NS_IMPL_ISUPPORTS(DialogCloseWatcherListener, nsIDOMEventListener) 88 89 // https://html.spec.whatwg.org/#computed-closed-by-state 90 void HTMLDialogElement::GetClosedBy(nsAString& aResult) const { 91 aResult.Truncate(); 92 MOZ_ASSERT(StaticPrefs::dom_dialog_light_dismiss_enabled()); 93 const nsAttrValue* val = mAttrs.GetAttr(nsGkAtoms::closedby); 94 // 1. If the state of dialog's closedby attribute is Auto: 95 if (!val || val->GetEnumValue() == kClosedbyAuto->value) { 96 // 1.1. If dialog's is modal is true, then return Close Request. 97 // 1.2. Return None. 98 const char* tag = 99 (IsInTopLayer() ? kClosedbyModalDefault->tag : kClosedbyDefault->tag); 100 AppendASCIItoUTF16(nsDependentCString(tag), aResult); 101 return; 102 } 103 // 2. Return the state of dialog's closedby attribute. 104 val->GetEnumString(aResult, true); 105 } 106 107 // https://html.spec.whatwg.org/#computed-closed-by-state 108 HTMLDialogElement::ClosedBy HTMLDialogElement::GetClosedBy() const { 109 if (!StaticPrefs::dom_dialog_light_dismiss_enabled()) { 110 return static_cast<ClosedBy>(IsInTopLayer() ? kClosedbyModalDefault->value 111 : kClosedbyDefault->value); 112 } 113 const nsAttrValue* val = mAttrs.GetAttr(nsGkAtoms::closedby); 114 // 1. If the state of dialog's closedby attribute is Auto: 115 if (!val || val->GetEnumValue() == kClosedbyAuto->value) { 116 // 1.1. If dialog's is modal is true, then return Close Request. 117 // 1.2. Return None. 118 return static_cast<ClosedBy>(IsInTopLayer() ? kClosedbyModalDefault->value 119 : kClosedbyDefault->value); 120 } 121 // 2. Return the state of dialog's closedby attribute. 122 return static_cast<ClosedBy>(val->GetEnumValue()); 123 } 124 125 bool HTMLDialogElement::ParseClosedByAttribute(const nsAString& aValue, 126 nsAttrValue& aResult) { 127 return aResult.ParseEnumValue(aValue, kClosedbyTable, 128 /* aCaseSensitive = */ false, kClosedbyAuto); 129 } 130 131 bool HTMLDialogElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, 132 const nsAString& aValue, 133 nsIPrincipal* aMaybeScriptedPrincipal, 134 nsAttrValue& aResult) { 135 if (aNamespaceID == kNameSpaceID_None) { 136 if (StaticPrefs::dom_dialog_light_dismiss_enabled() && 137 aAttribute == nsGkAtoms::closedby) { 138 return ParseClosedByAttribute(aValue, aResult); 139 } 140 } 141 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, 142 aMaybeScriptedPrincipal, aResult); 143 } 144 145 // https://html.spec.whatwg.org/#dom-dialog-close 146 // https://html.spec.whatwg.org/#close-the-dialog 147 void HTMLDialogElement::Close( 148 Element* aSource, const mozilla::dom::Optional<nsAString>& aReturnValue) { 149 // 1. If subject does not have an open attribute, then return. 150 if (!Open()) { 151 return; 152 } 153 154 // 2. Fire an event named beforetoggle, using ToggleEvent, with the oldState 155 // attribute initialized to "open", the newState attribute initialized to 156 // "closed", and the source attribute initialized to source at subject. 157 FireToggleEvent(u"open"_ns, u"closed"_ns, u"beforetoggle"_ns, aSource); 158 159 // 3. If subject does not have an open attribute, then return. 160 if (!Open()) { 161 return; 162 } 163 164 // 4. Queue a dialog toggle event task given subject, "open", "closed", and 165 // source. 166 QueueToggleEventTask(aSource); 167 168 // 5. Remove subject's open attribute. 169 SetOpen(false, IgnoreErrors()); 170 171 // 6. If is modal of subject is true, then request an element to be removed 172 // from the top layer given subject. 173 // 7. Let wasModal be the value of subject's is modal flag. 174 // 8. Set is modal of subject to false. 175 RemoveFromTopLayerIfNeeded(); 176 177 // 9. If result is not null, then set subject's returnValue attribute to 178 // result. 179 if (aReturnValue.WasPassed()) { 180 SetReturnValue(aReturnValue.Value()); 181 } 182 183 // 10. Set subject's request close return value to null. 184 ClearRequestCloseReturnValue(); 185 186 // 11. Set subject's request close source element to null. 187 mRequestCloseSourceElement = nullptr; 188 189 MOZ_ASSERT(!OwnerDoc()->DialogIsInOpenDialogsList(*this), 190 "Dialog should not being in Open Dialog List"); 191 192 // 12. If subject's previously focused element is not null, then: 193 // 12.1. Let element be subject's previously focused element. 194 RefPtr<Element> previouslyFocusedElement = 195 do_QueryReferent(mPreviouslyFocusedElement); 196 197 if (previouslyFocusedElement) { 198 // 12.2. Set subject's previously focused element to null. 199 mPreviouslyFocusedElement = nullptr; 200 201 // 12.3. If subject's node document's focused area of the document's DOM 202 // anchor is a shadow-including inclusive descendant of subject, or wasModal 203 // is true, then run the focusing steps for element; the viewport should not 204 // be scrolled by doing this step. 205 FocusOptions options; 206 options.mPreventScroll = true; 207 previouslyFocusedElement->Focus(options, CallerType::NonSystem, 208 IgnoredErrorResult()); 209 } 210 211 // 13. Queue an element task on the user interaction task source given the 212 // subject element to fire an event named close at subject. 213 RefPtr<AsyncEventDispatcher> eventDispatcher = 214 new AsyncEventDispatcher(this, u"close"_ns, CanBubble::eNo); 215 eventDispatcher->PostDOMEvent(); 216 } 217 218 // https://html.spec.whatwg.org/#dom-dialog-requestclose 219 // https://html.spec.whatwg.org/#dialog-request-close 220 void HTMLDialogElement::RequestClose( 221 Element* aSource, const mozilla::dom::Optional<nsAString>& aReturnValue) { 222 RefPtr closeWatcher = mCloseWatcher; 223 // 1. If subject does not have an open attribute, then return. 224 if (!Open()) { 225 return; 226 } 227 228 // 2. If subject is not connected or subject's node document is not fully 229 // active, then return. 230 if (!IsInComposedDoc() || !OwnerDoc()->IsFullyActive()) { 231 return; 232 } 233 234 // 3. Assert: subject's close watcher is not null. 235 if (StaticPrefs::dom_closewatcher_enabled()) { 236 MOZ_ASSERT(closeWatcher, "RequestClose needs mCloseWatcher"); 237 } 238 239 // 4. Set subject's enable close watcher for request close to true. 240 if (StaticPrefs::dom_closewatcher_enabled()) { 241 // XXX: Rather than store a "enable close watcher for request close" state, 242 // we set the CloseWatcher Enabled state to true manually here, and revert 243 // it lower down... 244 closeWatcher->SetEnabled(true); 245 } 246 247 // 5. Set subject's request close return value to returnValue. 248 if (aReturnValue.WasPassed()) { 249 SetRequestCloseReturnValue(aReturnValue.Value()); 250 } 251 252 // 6. Set subject's request close source element to source. 253 mRequestCloseSourceElement = do_GetWeakReference(aSource); 254 255 // 7. Request to close subject's close watcher with false. 256 if (StaticPrefs::dom_closewatcher_enabled()) { 257 closeWatcher->RequestToClose(false); 258 } else { 259 RunCancelDialogSteps(); 260 } 261 262 // 8. Set subject's enable close watcher for request close to false. 263 if (closeWatcher) { 264 // XXX: Rather than store a "enable close watcher for request close" state, 265 // we can simply set the close watcher enabled state to whatever it was 266 // before we set it to true (above). SetCloseWatcherEnabledState() will do 267 // this: 268 SetCloseWatcherEnabledState(); 269 } 270 } 271 272 RefPtr<Element> HTMLDialogElement::GetRequestCloseSourceElement() { 273 return do_QueryReferent(mRequestCloseSourceElement); 274 } 275 276 // https://html.spec.whatwg.org/#dom-dialog-show 277 void HTMLDialogElement::Show(ErrorResult& aError) { 278 // 1. If this has an open attribute and is modal of this is false, then 279 // return. 280 if (Open()) { 281 if (!IsInTopLayer()) { 282 return; 283 } 284 285 // 2. If this has an open attribute, then throw an "InvalidStateError" 286 // DOMException. 287 return aError.ThrowInvalidStateError( 288 "Cannot call show() on an open modal dialog."); 289 } 290 291 // 3. If the result of firing an event named beforetoggle, using ToggleEvent, 292 // with the cancelable attribute initialized to true, the oldState attribute 293 // initialized to "closed", and the newState attribute initialized to "open" 294 // at this is false, then return. 295 if (FireToggleEvent(u"closed"_ns, u"open"_ns, u"beforetoggle"_ns, nullptr)) { 296 return; 297 } 298 299 // 4. If this has an open attribute, then return. 300 if (Open()) { 301 return; 302 } 303 304 // 5. Queue a dialog toggle event task given this, "closed", and "open". 305 QueueToggleEventTask(nullptr); 306 307 // 6. Add an open attribute to this, whose value is the empty string. 308 SetOpen(true, IgnoreErrors()); 309 310 // 7. Set this's previously focused element to the focused element. 311 StorePreviouslyFocusedElement(); 312 313 // 8. Let document be this's node document. 314 315 // 9. Let hideUntil be the result of running topmost popover ancestor given 316 // this, document's showing hint popover list, null, and false. 317 RefPtr<nsINode> hideUntil = 318 GetTopmostPopoverAncestor(PopoverAttributeState::Auto, nullptr, false); 319 320 // 10. If hideUntil is null, then set hideUntil to the result of running 321 // topmost popover ancestor given this, document's showing auto popover list, 322 // null, and false. 323 // TODO(keithamus): Popover hint 324 325 // 11. If hideUntil is null, then set hideUntil to document. 326 if (!hideUntil) { 327 hideUntil = OwnerDoc(); 328 } 329 330 // 12. Run hide all popovers until given hideUntil, false, and true. 331 OwnerDoc()->HideAllPopoversUntil(*hideUntil, false, true); 332 333 // 13. Run the dialog focusing steps given this. 334 FocusDialog(); 335 } 336 337 bool HTMLDialogElement::Open() const { 338 MOZ_ASSERT(GetBoolAttr(nsGkAtoms::open) == 339 State().HasState(ElementState::OPEN)); 340 return State().HasState(ElementState::OPEN); 341 } 342 343 bool HTMLDialogElement::IsInTopLayer() const { 344 return State().HasState(ElementState::MODAL); 345 } 346 347 void HTMLDialogElement::AddToTopLayerIfNeeded() { 348 MOZ_ASSERT(IsInComposedDoc(), "AddToTopLayerIfNeeded needs IsInComposedDoc"); 349 if (IsInTopLayer()) { 350 return; 351 } 352 353 OwnerDoc()->AddModalDialog(*this); 354 355 // A change to the modal state may cause the CloseWatcher enabled state to 356 // change, if the `closedby` attribute is missing and therefore in the Auto 357 // (computed) state. 358 SetCloseWatcherEnabledState(); 359 } 360 361 void HTMLDialogElement::RemoveFromTopLayerIfNeeded() { 362 if (!IsInTopLayer()) { 363 return; 364 } 365 OwnerDoc()->RemoveModalDialog(*this); 366 367 // A change to the modal state may cause the CloseWatcher enabled state to 368 // change, if the `closedby` attribute is missing and therefore in the Auto 369 // (computed) state. 370 SetCloseWatcherEnabledState(); 371 } 372 373 void HTMLDialogElement::StorePreviouslyFocusedElement() { 374 if (Element* element = nsFocusManager::GetFocusedElementStatic()) { 375 if (NS_SUCCEEDED(nsContentUtils::CheckSameOrigin(this, element))) { 376 mPreviouslyFocusedElement = do_GetWeakReference(element); 377 } 378 } else if (Document* doc = GetComposedDoc()) { 379 // Looks like there's a discrepancy sometimes when focus is moved 380 // to a different in-process window. 381 if (nsIContent* unretargetedFocus = doc->GetUnretargetedFocusedContent()) { 382 mPreviouslyFocusedElement = do_GetWeakReference(unretargetedFocus); 383 } 384 } 385 } 386 387 nsresult HTMLDialogElement::BindToTree(BindContext& aContext, 388 nsINode& aParent) { 389 MOZ_TRY(nsGenericHTMLElement::BindToTree(aContext, aParent)); 390 391 // https://html.spec.whatwg.org/#the-dialog-element:html-element-insertion-steps 392 // 1. If insertedNode's node document is not fully active, then return. 393 // 2. If insertedNode is connected, then run the 394 // dialog setup steps given insertedNode. 395 if (Open() && IsInComposedDoc() && OwnerDoc()->IsFullyActive() && 396 !aContext.IsMove()) { 397 SetupSteps(); 398 } 399 400 return NS_OK; 401 } 402 403 // https://html.spec.whatwg.org/interactive-elements.html#the-dialog-element:html-element-removing-steps 404 void HTMLDialogElement::UnbindFromTree(UnbindContext& aContext) { 405 if (!aContext.IsMove()) { 406 // 1. If removedNode has an open attribute, then run the dialog cleanup 407 // steps given removedNode. 408 if (Open()) { 409 CleanupSteps(); 410 } 411 412 // 2. If removedNode's node document's top layer contains removedNode, then 413 // remove an element from the top layer immediately given removedNode. 414 RemoveFromTopLayerIfNeeded(); 415 416 // 3. Set is modal of removedNode to false. 417 } 418 419 nsGenericHTMLElement::UnbindFromTree(aContext); 420 } 421 422 // https://html.spec.whatwg.org/#show-a-modal-dialog 423 void HTMLDialogElement::ShowModal(Element* aSource, ErrorResult& aError) { 424 // 1. If subject has an open attribute and is modal of subject is true, then 425 // return. 426 if (Open()) { 427 if (IsInTopLayer()) { 428 return; 429 } 430 431 // 2. If subject has an open attribute, then throw an "InvalidStateError" 432 // DOMException. 433 return aError.ThrowInvalidStateError( 434 "Cannot call showModal() on an open non-modal dialog."); 435 } 436 437 // 3. If subject's node document is not fully active, then throw an 438 // "InvalidStateError" DOMException. 439 if (!OwnerDoc()->IsFullyActive()) { 440 return aError.ThrowInvalidStateError( 441 "The owner document is not fully active"); 442 } 443 444 // 4. If subject is not connected, then throw an "InvalidStateError" 445 // DOMException. 446 if (!IsInComposedDoc()) { 447 return aError.ThrowInvalidStateError("Dialog element is not connected"); 448 } 449 450 // 5. If subject is in the popover showing state, then throw an 451 // "InvalidStateError" DOMException. 452 if (IsPopoverOpen()) { 453 return aError.ThrowInvalidStateError( 454 "Dialog element is already an open popover."); 455 } 456 457 // 6. If the result of firing an event named beforetoggle, using 458 // ToggleEvent, with the cancelable attribute initialized to true, the 459 // oldState attribute initialized to "closed", and the newState attribute 460 // initialized to "open" at subject is false, then return. 461 if (FireToggleEvent(u"closed"_ns, u"open"_ns, u"beforetoggle"_ns, aSource)) { 462 return; 463 } 464 465 // 7. If subject has an open attribute, then return. 466 // 8. If subject is not connected, then return. 467 // 9. If subject is in the popover showing state, then return. 468 if (Open() || !IsInComposedDoc() || IsPopoverOpen()) { 469 return; 470 } 471 472 // 10. Queue a dialog toggle event task given subject, "closed", and "open". 473 QueueToggleEventTask(aSource); 474 475 // 11. Add an open attribute to subject, whose value is the empty string. 476 SetOpen(true, aError); 477 478 // 12. Assert: subject's close watcher is not null. 479 if (StaticPrefs::dom_closewatcher_enabled()) { 480 MOZ_ASSERT(mCloseWatcher, "ShowModal needs mCloseWatcher"); 481 } 482 483 // 13. Set is modal of subject to true. 484 // 14. Set subject's node document to be blocked by the modal dialog subject. 485 // 15. If subject's node document's top layer does not already contain 486 // subject, then add an element to the top layer given subject. 487 AddToTopLayerIfNeeded(); 488 489 // 16. Set subject's previously focused element to the focused element. 490 StorePreviouslyFocusedElement(); 491 492 // 17. Let document be subject's node document. 493 494 // 18. Let hideUntil be the result of running topmost popover ancestor given 495 // subject, document's showing hint popover list, null, and false. 496 RefPtr<nsINode> hideUntil = 497 GetTopmostPopoverAncestor(PopoverAttributeState::Auto, nullptr, false); 498 499 // 19. If hideUntil is null, then set hideUntil to the result of running 500 // topmost popover ancestor given subject, document's showing auto popover 501 // list, null, and false. 502 // TODO(keithamus): Popover hint 503 504 // 20. If hideUntil is null, then set hideUntil to document. 505 if (!hideUntil) { 506 hideUntil = OwnerDoc(); 507 } 508 509 // 21. Run hide all popovers until given hideUntil, false, and true. 510 OwnerDoc()->HideAllPopoversUntil(*hideUntil, false, true); 511 512 // 22. Run the dialog focusing steps given subject. 513 FocusDialog(); 514 515 aError.SuppressException(); 516 } 517 518 // https://html.spec.whatwg.org/#the-dialog-element:concept-element-attributes-change-ext 519 void HTMLDialogElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, 520 const nsAttrValue* aValue, 521 const nsAttrValue* aOldValue, 522 nsIPrincipal* aMaybeScriptedPrincipal, 523 bool aNotify) { 524 nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, aOldValue, 525 aMaybeScriptedPrincipal, aNotify); 526 // 1. If namespace is not null, then return. 527 if (aNameSpaceID != kNameSpaceID_None) { 528 return; 529 } 530 531 // https://html.spec.whatwg.org/#set-the-dialog-close-watcher 532 // https://github.com/whatwg/html/issues/11267 533 // XXX: CloseWatcher currently uses a `getEnabledState` algorithm to set a 534 // boolean, but this is quite a lot of additional infrastructure which could 535 // be simplified by CloseWatcher having an "Enabled" state. 536 // If the closedby attribute changes, it may or may not toggle the 537 // CloseWatcher enabled state. 538 if (aName == nsGkAtoms::closedby) { 539 SetCloseWatcherEnabledState(); 540 } 541 542 // 2. If localName is not open, then return. 543 if (aName != nsGkAtoms::open) { 544 return; 545 } 546 547 bool wasOpen = !!aOldValue; 548 bool isOpen = !!aValue; 549 550 MOZ_ASSERT(GetBoolAttr(nsGkAtoms::open) == isOpen); 551 SetStates(ElementState::OPEN, isOpen); 552 553 // 3. If value is null and oldValue is not null, then run the dialog cleanup 554 // steps given element. 555 if (!isOpen && wasOpen) { 556 CleanupSteps(); 557 } 558 559 // 4. If element's node document is not fully active, then return. 560 if (!OwnerDoc()->IsFullyActive()) { 561 return; 562 } 563 564 // 5. If element is not connected, then return. 565 if (!IsInComposedDoc()) { 566 return; 567 } 568 569 // 6. If value is not null and oldValue is null, then run the dialog setup 570 // steps given element. 571 if (isOpen && !wasOpen) { 572 SetupSteps(); 573 } 574 } 575 576 void HTMLDialogElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) { 577 if (mToggleEventDispatcher == aEvent) { 578 mToggleEventDispatcher = nullptr; 579 } 580 } 581 582 void HTMLDialogElement::FocusDialog() { 583 // 1) If subject is inert, return. 584 // 2) Let control be the first descendant element of subject, in tree 585 // order, that is not inert and has the autofocus attribute specified. 586 RefPtr<Document> doc = OwnerDoc(); 587 if (IsInComposedDoc()) { 588 doc->FlushPendingNotifications(FlushType::Frames); 589 } 590 591 RefPtr<Element> control = HasAttr(nsGkAtoms::autofocus) 592 ? this 593 : GetFocusDelegate(IsFocusableFlags(0)); 594 595 // If there isn't one of those either, then let control be subject. 596 if (!control) { 597 control = this; 598 } 599 600 FocusCandidate(control, IsInTopLayer()); 601 } 602 603 int32_t HTMLDialogElement::TabIndexDefault() { return 0; } 604 605 void HTMLDialogElement::QueueCancelDialog() { 606 // queues an element task on the user interaction task source 607 OwnerDoc()->Dispatch( 608 NewRunnableMethod("HTMLDialogElement::RunCancelDialogSteps", this, 609 &HTMLDialogElement::RunCancelDialogSteps)); 610 } 611 612 void HTMLDialogElement::RunCancelDialogSteps() { 613 // 1) Let close be the result of firing an event named cancel at dialog, with 614 // the cancelable attribute initialized to true. 615 bool defaultAction = true; 616 nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u"cancel"_ns, 617 CanBubble::eNo, Cancelable::eYes, 618 &defaultAction); 619 620 // 2) If close is true and dialog has an open attribute, then close the dialog 621 // with ~~no return value.~~ 622 // XXX(keithamus): RequestClose's steps expect the return value to be 623 // RequestCloseReturnValue. RunCancelDialogSteps has been refactored out of 624 // the spec, over CloseWatcher though, so one day this code will need to be 625 // refactored when the CloseWatcher specifications settle. 626 if (defaultAction) { 627 Optional<nsAString> retValue; 628 GetRequestCloseReturnValue(retValue); 629 RefPtr<Element> source = GetRequestCloseSourceElement(); 630 Close(source, retValue); 631 } 632 } 633 634 bool HTMLDialogElement::IsValidCommandAction(Command aCommand) const { 635 return nsGenericHTMLElement::IsValidCommandAction(aCommand) || 636 aCommand == Command::ShowModal || aCommand == Command::Close || 637 aCommand == Command::RequestClose; 638 } 639 640 bool HTMLDialogElement::HandleCommandInternal(Element* aSource, 641 Command aCommand, 642 ErrorResult& aRv) { 643 if (nsGenericHTMLElement::HandleCommandInternal(aSource, aCommand, aRv)) { 644 return true; 645 } 646 647 MOZ_ASSERT(IsValidCommandAction(aCommand)); 648 649 if ((aCommand == Command::Close || aCommand == Command::RequestClose) && 650 Open()) { 651 Optional<nsAString> retValueOpt; 652 nsString retValue; 653 if (aSource->HasAttr(nsGkAtoms::value)) { 654 if (auto* button = HTMLButtonElement::FromNodeOrNull(aSource)) { 655 button->GetValue(retValue); 656 retValueOpt = &retValue; 657 } 658 } 659 if (aCommand == Command::Close) { 660 Close(aSource, retValueOpt); 661 } else { 662 MOZ_ASSERT(aCommand == Command::RequestClose); 663 RequestClose(aSource, retValueOpt); 664 } 665 return true; 666 } 667 668 if (IsInComposedDoc() && !Open() && aCommand == Command::ShowModal) { 669 ShowModal(aSource, aRv); 670 return true; 671 } 672 673 return false; 674 } 675 676 void HTMLDialogElement::QueueToggleEventTask(Element* aSource) { 677 nsAutoString oldState; 678 auto newState = Open() ? u"closed"_ns : u"open"_ns; 679 if (mToggleEventDispatcher) { 680 oldState.Truncate(); 681 static_cast<ToggleEvent*>(mToggleEventDispatcher->mEvent.get()) 682 ->GetOldState(oldState); 683 mToggleEventDispatcher->Cancel(); 684 } else { 685 oldState.Assign(Open() ? u"open"_ns : u"closed"_ns); 686 } 687 RefPtr<ToggleEvent> toggleEvent = CreateToggleEvent( 688 u"toggle"_ns, oldState, newState, Cancelable::eNo, aSource); 689 mToggleEventDispatcher = new AsyncEventDispatcher(this, toggleEvent.forget()); 690 mToggleEventDispatcher->PostDOMEvent(); 691 } 692 693 // https://html.spec.whatwg.org/#set-the-dialog-close-watcher 694 void HTMLDialogElement::SetDialogCloseWatcherIfNeeded() { 695 MOZ_ASSERT(StaticPrefs::dom_closewatcher_enabled(), "CloseWatcher enabled"); 696 // 1. Assert: dialog's close watcher is null. 697 MOZ_ASSERT(!mCloseWatcher); 698 699 // 2. Assert: dialog has an open attribute and dialog's node document is fully 700 // active. 701 RefPtr<Document> doc = OwnerDoc(); 702 RefPtr window = doc->GetInnerWindow(); 703 MOZ_ASSERT(Open() && window && window->IsFullyActive()); 704 705 // 3. Set dialog's close watcher to the result of establishing a close watcher 706 // given dialog's relevant global object, with: 707 mCloseWatcher = new CloseWatcher(window); 708 RefPtr<DialogCloseWatcherListener> eventListener = 709 new DialogCloseWatcherListener(this); 710 711 // - cancelAction given canPreventClose being to return the result of firing 712 // an event named cancel at dialog, with the cancelable attribute initialized 713 // to canPreventClose. 714 mCloseWatcher->AddSystemEventListener(u"cancel"_ns, eventListener, 715 false /* aUseCapture */, 716 false /* aWantsUntrusted */); 717 718 // - closeAction being to close the dialog given dialog and dialog's request 719 // close return value. 720 mCloseWatcher->AddSystemEventListener(u"close"_ns, eventListener, 721 false /* aUseCapture */, 722 false /* aWantsUntrusted */); 723 724 // - getEnabledState being to return true if dialog's enable close watcher for 725 // requestClose() is true or dialog's computed closed-by state is not None; 726 // otherwise false. 727 // 728 // XXX: Rather than creating a function pointer to manage the state of two 729 // boolean conditions, we set the enabled state of the close watcher 730 // explicitly whenever the state of those two conditions change. The first 731 // condition "enable close watcher for requestclose" is managed in 732 // RequestClose(), the other condition is managed by this function: 733 SetCloseWatcherEnabledState(); 734 735 mCloseWatcher->AddToWindowsCloseWatcherManager(); 736 } 737 738 // https://html.spec.whatwg.org/multipage#dialog-setup-steps 739 void HTMLDialogElement::SetupSteps() { 740 // 1. Assert: subject has an open attribute. 741 MOZ_ASSERT(Open()); 742 743 // 2. Assert: subject is connected. 744 MOZ_ASSERT(IsInComposedDoc(), "Dialog SetupSteps needs IsInComposedDoc"); 745 746 // 3. Assert: subject's node document's open dialogs list does not contain 747 // subject. 748 MOZ_ASSERT(!OwnerDoc()->DialogIsInOpenDialogsList(*this)); 749 750 // 4. Add subject to subject's node document's open dialogs list. 751 OwnerDoc()->AddOpenDialog(*this); 752 753 // 5. Set the dialog close watcher with subject. 754 if (StaticPrefs::dom_closewatcher_enabled()) { 755 SetDialogCloseWatcherIfNeeded(); 756 } 757 } 758 759 void HTMLDialogElement::SetCloseWatcherEnabledState() { 760 if (StaticPrefs::dom_closewatcher_enabled() && mCloseWatcher) { 761 mCloseWatcher->SetEnabled(GetClosedBy() != ClosedBy::None); 762 } 763 } 764 765 // https://html.spec.whatwg.org/#dialog-cleanup-steps 766 void HTMLDialogElement::CleanupSteps() { 767 // 1. Remove subject from subject's node document's open dialogs list. 768 OwnerDoc()->RemoveOpenDialog(*this); 769 770 // 2. If subject's close watcher is not null, and subject does not have an 771 // open attribute, then: 772 if (mCloseWatcher) { 773 // 3. Destroy subject's close watcher. 774 mCloseWatcher->Destroy(); 775 776 // 4. Set subject's close watcher to null. 777 mCloseWatcher = nullptr; 778 } 779 } 780 781 JSObject* HTMLDialogElement::WrapNode(JSContext* aCx, 782 JS::Handle<JSObject*> aGivenProto) { 783 return HTMLDialogElement_Binding::Wrap(aCx, this, aGivenProto); 784 } 785 786 } // namespace mozilla::dom