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 #include "mozilla/dom/ElementBinding.h"
9 #include "mozilla/dom/HTMLDialogElementBinding.h"
10 #include "mozilla/dom/HTMLUnknownElement.h"
11 #include "mozilla/StaticPrefs_dom.h"
12
13 #include "nsContentUtils.h"
14 #include "nsFocusManager.h"
15 #include "nsIFrame.h"
16
17 // Expand NS_IMPL_NS_NEW_HTML_ELEMENT(Dialog) with pref check
NS_NewHTMLDialogElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo,mozilla::dom::FromParser aFromParser)18 nsGenericHTMLElement* NS_NewHTMLDialogElement(
19 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
20 mozilla::dom::FromParser aFromParser) {
21 RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
22 auto* nim = nodeInfo->NodeInfoManager();
23 bool isChromeDocument = nsContentUtils::IsChromeDoc(nodeInfo->GetDocument());
24 if (mozilla::StaticPrefs::dom_dialog_element_enabled() || isChromeDocument) {
25 return new (nim) mozilla::dom::HTMLDialogElement(nodeInfo.forget());
26 }
27 return new (nim) mozilla::dom::HTMLUnknownElement(nodeInfo.forget());
28 }
29
30 namespace mozilla::dom {
31
32 HTMLDialogElement::~HTMLDialogElement() = default;
33
NS_IMPL_ELEMENT_CLONE(HTMLDialogElement)34 NS_IMPL_ELEMENT_CLONE(HTMLDialogElement)
35
36 bool HTMLDialogElement::IsDialogEnabled(JSContext* aCx,
37 JS::Handle<JSObject*> aObj) {
38 return StaticPrefs::dom_dialog_element_enabled() ||
39 nsContentUtils::IsSystemCaller(aCx);
40 }
41
Close(const mozilla::dom::Optional<nsAString> & aReturnValue)42 void HTMLDialogElement::Close(
43 const mozilla::dom::Optional<nsAString>& aReturnValue) {
44 if (!Open()) {
45 return;
46 }
47 if (aReturnValue.WasPassed()) {
48 SetReturnValue(aReturnValue.Value());
49 }
50
51 SetOpen(false, IgnoreErrors());
52
53 RemoveFromTopLayerIfNeeded();
54
55 RefPtr<Element> previouslyFocusedElement =
56 do_QueryReferent(mPreviouslyFocusedElement);
57
58 if (previouslyFocusedElement) {
59 mPreviouslyFocusedElement = nullptr;
60
61 FocusOptions options;
62 options.mPreventScroll = true;
63 previouslyFocusedElement->Focus(options, CallerType::NonSystem,
64 IgnoredErrorResult());
65 }
66
67 RefPtr<AsyncEventDispatcher> eventDispatcher =
68 new AsyncEventDispatcher(this, u"close"_ns, CanBubble::eNo);
69 eventDispatcher->PostDOMEvent();
70 }
71
Show()72 void HTMLDialogElement::Show() {
73 if (Open()) {
74 return;
75 }
76 SetOpen(true, IgnoreErrors());
77
78 StorePreviouslyFocusedElement();
79
80 FocusDialog();
81 }
82
IsInTopLayer() const83 bool HTMLDialogElement::IsInTopLayer() const {
84 return State().HasState(NS_EVENT_STATE_MODAL_DIALOG);
85 }
86
AddToTopLayerIfNeeded()87 void HTMLDialogElement::AddToTopLayerIfNeeded() {
88 if (IsInTopLayer()) {
89 return;
90 }
91
92 Document* doc = OwnerDoc();
93 doc->TopLayerPush(this);
94 doc->SetBlockedByModalDialog(*this);
95 AddStates(NS_EVENT_STATE_MODAL_DIALOG);
96 }
97
RemoveFromTopLayerIfNeeded()98 void HTMLDialogElement::RemoveFromTopLayerIfNeeded() {
99 if (!IsInTopLayer()) {
100 return;
101 }
102 auto predictFunc = [&](Element* element) { return element == this; };
103
104 Document* doc = OwnerDoc();
105 DebugOnly<Element*> removedElement = doc->TopLayerPop(predictFunc);
106 MOZ_ASSERT(removedElement == this);
107 RemoveStates(NS_EVENT_STATE_MODAL_DIALOG);
108 doc->UnsetBlockedByModalDialog(*this);
109 }
110
StorePreviouslyFocusedElement()111 void HTMLDialogElement::StorePreviouslyFocusedElement() {
112 if (Document* doc = GetComposedDoc()) {
113 if (nsIContent* unretargetedFocus = doc->GetUnretargetedFocusedContent()) {
114 mPreviouslyFocusedElement =
115 do_GetWeakReference(unretargetedFocus->AsElement());
116 }
117 }
118 }
119
UnbindFromTree(bool aNullParent)120 void HTMLDialogElement::UnbindFromTree(bool aNullParent) {
121 RemoveFromTopLayerIfNeeded();
122 nsGenericHTMLElement::UnbindFromTree(aNullParent);
123 }
124
ShowModal(ErrorResult & aError)125 void HTMLDialogElement::ShowModal(ErrorResult& aError) {
126 if (!IsInComposedDoc()) {
127 aError.ThrowInvalidStateError("Dialog element is not connected");
128 return;
129 }
130
131 if (Open()) {
132 aError.ThrowInvalidStateError(
133 "Dialog element already has an 'open' attribute");
134 return;
135 }
136
137 AddToTopLayerIfNeeded();
138
139 SetOpen(true, aError);
140
141 StorePreviouslyFocusedElement();
142
143 FocusDialog();
144
145 aError.SuppressException();
146 }
147
FocusDialog()148 void HTMLDialogElement::FocusDialog() {
149 // 1) If subject is inert, return.
150 // 2) Let control be the first descendant element of subject, in tree
151 // order, that is not inert and has the autofocus attribute specified.
152 if (RefPtr<Document> doc = GetComposedDoc()) {
153 doc->FlushPendingNotifications(FlushType::Frames);
154 }
155
156 Element* control = nullptr;
157 for (auto* child = GetFirstChild(); child; child = child->GetNextNode(this)) {
158 auto* element = Element::FromNode(child);
159 if (!element) {
160 continue;
161 }
162 nsIFrame* frame = element->GetPrimaryFrame();
163 if (!frame || !frame->IsFocusable()) {
164 continue;
165 }
166 if (element->HasAttr(nsGkAtoms::autofocus)) {
167 // Find the first descendant of element of subject that this not inert and
168 // has autofucus attribute.
169 control = element;
170 break;
171 }
172 if (!control) {
173 // If there isn't one, then let control be the first non-inert descendant
174 // element of subject, in tree order.
175 control = element;
176 }
177 }
178
179 // If there isn't one of those either, then let control be subject.
180 if (!control) {
181 control = this;
182 }
183
184 // 3) Run the focusing steps for control.
185 ErrorResult rv;
186 nsIFrame* frame = control->GetPrimaryFrame();
187 if (frame && frame->IsFocusable()) {
188 control->Focus(FocusOptions(), CallerType::NonSystem, rv);
189 if (rv.Failed()) {
190 return;
191 }
192 } else if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
193 // Clear the focus which ends up making the body gets focused
194 fm->ClearFocus(OwnerDoc()->GetWindow());
195 }
196
197 // 4) Let topDocument be the active document of control's node document's
198 // browsing context's top-level browsing context.
199 // 5) If control's node document's origin is not the same as the origin of
200 // topDocument, then return.
201 BrowsingContext* bc = control->OwnerDoc()->GetBrowsingContext();
202 if (bc && bc->SameOriginWithTop()) {
203 if (nsCOMPtr<nsIDocShell> docShell = bc->Top()->GetDocShell()) {
204 if (Document* topDocument = docShell->GetExtantDocument()) {
205 // 6) Empty topDocument's autofocus candidates.
206 // 7) Set topDocument's autofocus processed flag to true.
207 topDocument->SetAutoFocusFired();
208 }
209 }
210 }
211 }
212
QueueCancelDialog()213 void HTMLDialogElement::QueueCancelDialog() {
214 // queues an element task on the user interaction task source
215 OwnerDoc()
216 ->EventTargetFor(TaskCategory::UI)
217 ->Dispatch(NewRunnableMethod("HTMLDialogElement::RunCancelDialogSteps",
218 this,
219 &HTMLDialogElement::RunCancelDialogSteps));
220 }
221
RunCancelDialogSteps()222 void HTMLDialogElement::RunCancelDialogSteps() {
223 // 1) Let close be the result of firing an event named cancel at dialog, with
224 // the cancelable attribute initialized to true.
225 bool defaultAction = true;
226 nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u"cancel"_ns,
227 CanBubble::eNo, Cancelable::eYes,
228 &defaultAction);
229
230 // 2) If close is true and dialog has an open attribute, then close the dialog
231 // with no return value.
232 if (defaultAction) {
233 Optional<nsAString> retValue;
234 Close(retValue);
235 }
236 }
237
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)238 JSObject* HTMLDialogElement::WrapNode(JSContext* aCx,
239 JS::Handle<JSObject*> aGivenProto) {
240 return HTMLDialogElement_Binding::Wrap(aCx, this, aGivenProto);
241 }
242
243 } // namespace mozilla::dom
244