1/*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "core/html/HTMLDialogElement.h"
28
29#include "bindings/core/v8/ExceptionState.h"
30#include "core/accessibility/AXObjectCache.h"
31#include "core/dom/ExceptionCode.h"
32#include "core/dom/NodeTraversal.h"
33#include "core/events/Event.h"
34#include "core/frame/UseCounter.h"
35#include "core/html/HTMLFormControlElement.h"
36#include "core/frame/FrameView.h"
37#include "core/rendering/RenderBlock.h"
38#include "core/rendering/style/RenderStyle.h"
39
40namespace blink {
41
42using namespace HTMLNames;
43
44// This function chooses the focused element when showModal() is invoked, as described in the spec for showModal().
45static void setFocusForModalDialog(HTMLDialogElement* dialog)
46{
47    Element* focusableDescendant = 0;
48    Node* next = 0;
49    for (Node* node = dialog->firstChild(); node; node = next) {
50        if (isHTMLDialogElement(*node))
51            next = NodeTraversal::nextSkippingChildren(*node, dialog);
52        else
53            next = NodeTraversal::next(*node, dialog);
54
55        if (!node->isElementNode())
56            continue;
57        Element* element = toElement(node);
58        if (element->isFormControlElement()) {
59            HTMLFormControlElement* control = toHTMLFormControlElement(node);
60            if (control->isAutofocusable()) {
61                control->focus();
62                return;
63            }
64        }
65        if (!focusableDescendant && element->isFocusable())
66            focusableDescendant = element;
67    }
68
69    if (focusableDescendant) {
70        focusableDescendant->focus();
71        return;
72    }
73
74    if (dialog->isFocusable()) {
75        dialog->focus();
76        return;
77    }
78
79    dialog->document().setFocusedElement(nullptr);
80}
81
82static void inertSubtreesChanged(Document& document)
83{
84    // When a modal dialog opens or closes, nodes all over the accessibility
85    // tree can change inertness which means they must be added or removed from
86    // the tree. The most foolproof way is to clear the entire tree and rebuild
87    // it, though a more clever way is probably possible.
88    Document& topDocument = document.topDocument();
89    topDocument.clearAXObjectCache();
90    if (AXObjectCache* cache = topDocument.axObjectCache())
91        cache->childrenChanged(cache->getOrCreate(&topDocument));
92}
93
94inline HTMLDialogElement::HTMLDialogElement(Document& document)
95    : HTMLElement(dialogTag, document)
96    , m_centeringMode(NotCentered)
97    , m_centeredPosition(0)
98    , m_returnValue("")
99{
100    UseCounter::count(document, UseCounter::DialogElement);
101}
102
103DEFINE_NODE_FACTORY(HTMLDialogElement)
104
105void HTMLDialogElement::close(const String& returnValue, ExceptionState& exceptionState)
106{
107    if (!fastHasAttribute(openAttr)) {
108        exceptionState.throwDOMException(InvalidStateError, "The element does not have an 'open' attribute, and therefore cannot be closed.");
109        return;
110    }
111    closeDialog(returnValue);
112}
113
114void HTMLDialogElement::closeDialog(const String& returnValue)
115{
116    if (!fastHasAttribute(openAttr))
117        return;
118    setBooleanAttribute(openAttr, false);
119
120    HTMLDialogElement* activeModalDialog = document().activeModalDialog();
121    document().removeFromTopLayer(this);
122    if (activeModalDialog == this)
123        inertSubtreesChanged(document());
124
125    if (!returnValue.isNull())
126        m_returnValue = returnValue;
127
128    dispatchScopedEvent(Event::create(EventTypeNames::close));
129}
130
131void HTMLDialogElement::forceLayoutForCentering()
132{
133    m_centeringMode = NeedsCentering;
134    document().updateLayoutIgnorePendingStylesheets();
135    if (m_centeringMode == NeedsCentering)
136        setNotCentered();
137}
138
139void HTMLDialogElement::show()
140{
141    if (fastHasAttribute(openAttr))
142        return;
143    setBooleanAttribute(openAttr, true);
144}
145
146void HTMLDialogElement::showModal(ExceptionState& exceptionState)
147{
148    if (fastHasAttribute(openAttr)) {
149        exceptionState.throwDOMException(InvalidStateError, "The element already has an 'open' attribute, and therefore cannot be opened modally.");
150        return;
151    }
152    if (!inDocument()) {
153        exceptionState.throwDOMException(InvalidStateError, "The element is not in a Document.");
154        return;
155    }
156
157    document().addToTopLayer(this);
158    setBooleanAttribute(openAttr, true);
159
160    // Throw away the AX cache first, so the subsequent steps don't have a chance of queuing up
161    // AX events on objects that would be invalidated when the cache is thrown away.
162    inertSubtreesChanged(document());
163
164    forceLayoutForCentering();
165    setFocusForModalDialog(this);
166}
167
168void HTMLDialogElement::removedFrom(ContainerNode* insertionPoint)
169{
170    HTMLElement::removedFrom(insertionPoint);
171    setNotCentered();
172    // FIXME: We should call inertSubtreesChanged() here.
173}
174
175void HTMLDialogElement::setCentered(LayoutUnit centeredPosition)
176{
177    ASSERT(m_centeringMode == NeedsCentering);
178    m_centeredPosition = centeredPosition;
179    m_centeringMode = Centered;
180}
181
182void HTMLDialogElement::setNotCentered()
183{
184    m_centeringMode = NotCentered;
185}
186
187bool HTMLDialogElement::isPresentationAttribute(const QualifiedName& name) const
188{
189    // FIXME: Workaround for <https://bugs.webkit.org/show_bug.cgi?id=91058>: modifying an attribute for which there is an attribute selector
190    // in html.css sometimes does not trigger a style recalc.
191    if (name == openAttr)
192        return true;
193
194    return HTMLElement::isPresentationAttribute(name);
195}
196
197void HTMLDialogElement::defaultEventHandler(Event* event)
198{
199    if (event->type() == EventTypeNames::cancel) {
200        closeDialog();
201        event->setDefaultHandled();
202        return;
203    }
204    HTMLElement::defaultEventHandler(event);
205}
206
207} // namespace blink
208