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