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 40 namespace blink { 41 42 using namespace HTMLNames; 43 44 // This function chooses the focused element when showModal() is invoked, as described in the spec for showModal(). 45 static 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 82 static 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 94 inline 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 103 DEFINE_NODE_FACTORY(HTMLDialogElement) 104 105 void 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 114 void 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 131 void HTMLDialogElement::forceLayoutForCentering() 132 { 133 m_centeringMode = NeedsCentering; 134 document().updateLayoutIgnorePendingStylesheets(); 135 if (m_centeringMode == NeedsCentering) 136 setNotCentered(); 137 } 138 139 void HTMLDialogElement::show() 140 { 141 if (fastHasAttribute(openAttr)) 142 return; 143 setBooleanAttribute(openAttr, true); 144 } 145 146 void 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 168 void HTMLDialogElement::removedFrom(ContainerNode* insertionPoint) 169 { 170 HTMLElement::removedFrom(insertionPoint); 171 setNotCentered(); 172 // FIXME: We should call inertSubtreesChanged() here. 173 } 174 175 void HTMLDialogElement::setCentered(LayoutUnit centeredPosition) 176 { 177 ASSERT(m_centeringMode == NeedsCentering); 178 m_centeredPosition = centeredPosition; 179 m_centeringMode = Centered; 180 } 181 182 void HTMLDialogElement::setNotCentered() 183 { 184 m_centeringMode = NotCentered; 185 } 186 187 bool 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 197 void 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