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/. */
7 #include "mozilla/dom/HTMLDialogElement.h"
8 #include "mozilla/dom/ElementBinding.h"
9 #include "mozilla/dom/HTMLDialogElementBinding.h"
11 #include "nsContentUtils.h"
12 #include "nsFocusManager.h"
15 NS_IMPL_NS_NEW_HTML_ELEMENT(Dialog
)
17 namespace mozilla::dom
{
19 HTMLDialogElement::~HTMLDialogElement() = default;
21 NS_IMPL_ELEMENT_CLONE(HTMLDialogElement
)
23 void HTMLDialogElement::Close(
24 const mozilla::dom::Optional
<nsAString
>& aReturnValue
) {
28 if (aReturnValue
.WasPassed()) {
29 SetReturnValue(aReturnValue
.Value());
32 SetOpen(false, IgnoreErrors());
34 RemoveFromTopLayerIfNeeded();
36 RefPtr
<Element
> previouslyFocusedElement
=
37 do_QueryReferent(mPreviouslyFocusedElement
);
39 if (previouslyFocusedElement
) {
40 mPreviouslyFocusedElement
= nullptr;
43 options
.mPreventScroll
= true;
44 previouslyFocusedElement
->Focus(options
, CallerType::NonSystem
,
45 IgnoredErrorResult());
48 RefPtr
<AsyncEventDispatcher
> eventDispatcher
=
49 new AsyncEventDispatcher(this, u
"close"_ns
, CanBubble::eNo
);
50 eventDispatcher
->PostDOMEvent();
53 void HTMLDialogElement::Show(ErrorResult
& aError
) {
55 if (!IsInTopLayer()) {
58 return aError
.ThrowInvalidStateError(
59 "Cannot call show() on an open modal dialog.");
62 SetOpen(true, IgnoreErrors());
64 StorePreviouslyFocusedElement();
66 RefPtr
<nsINode
> hideUntil
= GetTopmostPopoverAncestor(nullptr, false);
68 hideUntil
= OwnerDoc();
71 OwnerDoc()->HideAllPopoversUntil(*hideUntil
, false, true);
75 bool HTMLDialogElement::IsInTopLayer() const {
76 return State().HasState(ElementState::MODAL
);
79 void HTMLDialogElement::AddToTopLayerIfNeeded() {
80 MOZ_ASSERT(IsInComposedDoc());
85 OwnerDoc()->AddModalDialog(*this);
88 void HTMLDialogElement::RemoveFromTopLayerIfNeeded() {
89 if (!IsInTopLayer()) {
92 OwnerDoc()->RemoveModalDialog(*this);
95 void HTMLDialogElement::StorePreviouslyFocusedElement() {
96 if (Element
* element
= nsFocusManager::GetFocusedElementStatic()) {
97 if (NS_SUCCEEDED(nsContentUtils::CheckSameOrigin(this, element
))) {
98 mPreviouslyFocusedElement
= do_GetWeakReference(element
);
100 } else if (Document
* doc
= GetComposedDoc()) {
101 // Looks like there's a discrepancy sometimes when focus is moved
102 // to a different in-process window.
103 if (nsIContent
* unretargetedFocus
= doc
->GetUnretargetedFocusedContent()) {
104 mPreviouslyFocusedElement
= do_GetWeakReference(unretargetedFocus
);
109 void HTMLDialogElement::UnbindFromTree(UnbindContext
& aContext
) {
110 RemoveFromTopLayerIfNeeded();
111 nsGenericHTMLElement::UnbindFromTree(aContext
);
114 void HTMLDialogElement::ShowModal(ErrorResult
& aError
) {
116 if (IsInTopLayer()) {
119 return aError
.ThrowInvalidStateError(
120 "Cannot call showModal() on an open non-modal dialog.");
123 if (!IsInComposedDoc()) {
124 return aError
.ThrowInvalidStateError("Dialog element is not connected");
127 if (IsPopoverOpen()) {
128 return aError
.ThrowInvalidStateError(
129 "Dialog element is already an open popover.");
132 AddToTopLayerIfNeeded();
134 SetOpen(true, aError
);
136 StorePreviouslyFocusedElement();
138 RefPtr
<nsINode
> hideUntil
= GetTopmostPopoverAncestor(nullptr, false);
140 hideUntil
= OwnerDoc();
143 OwnerDoc()->HideAllPopoversUntil(*hideUntil
, false, true);
146 aError
.SuppressException();
149 void HTMLDialogElement::FocusDialog() {
150 // 1) If subject is inert, return.
151 // 2) Let control be the first descendant element of subject, in tree
152 // order, that is not inert and has the autofocus attribute specified.
153 RefPtr
<Document
> doc
= OwnerDoc();
154 if (IsInComposedDoc()) {
155 doc
->FlushPendingNotifications(FlushType::Frames
);
158 RefPtr
<Element
> control
= HasAttr(nsGkAtoms::autofocus
)
160 : GetFocusDelegate(false /* aWithMouse */);
162 // If there isn't one of those either, then let control be subject.
167 FocusCandidate(control
, IsInTopLayer());
170 int32_t HTMLDialogElement::TabIndexDefault() { return 0; }
172 void HTMLDialogElement::QueueCancelDialog() {
173 // queues an element task on the user interaction task source
174 OwnerDoc()->Dispatch(
175 NewRunnableMethod("HTMLDialogElement::RunCancelDialogSteps", this,
176 &HTMLDialogElement::RunCancelDialogSteps
));
179 void HTMLDialogElement::RunCancelDialogSteps() {
180 // 1) Let close be the result of firing an event named cancel at dialog, with
181 // the cancelable attribute initialized to true.
182 bool defaultAction
= true;
183 nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u
"cancel"_ns
,
184 CanBubble::eNo
, Cancelable::eYes
,
187 // 2) If close is true and dialog has an open attribute, then close the dialog
188 // with no return value.
190 Optional
<nsAString
> retValue
;
195 JSObject
* HTMLDialogElement::WrapNode(JSContext
* aCx
,
196 JS::Handle
<JSObject
*> aGivenProto
) {
197 return HTMLDialogElement_Binding::Wrap(aCx
, this, aGivenProto
);
200 } // namespace mozilla::dom