tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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