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/v8/ExceptionState.h" 30 #include "core/accessibility/AXObjectCache.h" 31 #include "core/dom/ExceptionCode.h" 32 #include "core/dom/NodeTraversal.h" 33 #include "core/html/HTMLFormControlElement.h" 34 #include "core/frame/FrameView.h" 35 #include "core/rendering/RenderBlock.h" 36 #include "core/rendering/style/RenderStyle.h" 37 38 namespace WebCore { 39 40 using namespace HTMLNames; 41 42 // This function chooses the focused element when showModal() is invoked, as described in the spec for showModal(). 43 static void setFocusForModalDialog(HTMLDialogElement* dialog) 44 { 45 Element* focusableDescendant = 0; 46 Node* next = 0; 47 for (Node* node = dialog->firstChild(); node; node = next) { 48 if (isHTMLDialogElement(*node)) 49 next = NodeTraversal::nextSkippingChildren(*node, dialog); 50 else 51 next = NodeTraversal::next(*node, dialog); 52 53 if (!node->isElementNode()) 54 continue; 55 Element* element = toElement(node); 56 if (element->isFormControlElement()) { 57 HTMLFormControlElement* control = toHTMLFormControlElement(node); 58 if (control->isAutofocusable()) { 59 control->focus(); 60 return; 61 } 62 } 63 if (!focusableDescendant && element->isFocusable()) 64 focusableDescendant = element; 65 } 66 67 if (focusableDescendant) { 68 focusableDescendant->focus(); 69 return; 70 } 71 72 if (dialog->isFocusable()) { 73 dialog->focus(); 74 return; 75 } 76 77 dialog->document().setFocusedElement(nullptr); 78 } 79 80 static void inertSubtreesChanged(Document& document) 81 { 82 // When a modal dialog opens or closes, nodes all over the accessibility 83 // tree can change inertness which means they must be added or removed from 84 // the tree. The most foolproof way is to clear the entire tree and rebuild 85 // it, though a more clever way is probably possible. 86 Document& topDocument = document.topDocument(); 87 topDocument.clearAXObjectCache(); 88 if (AXObjectCache* cache = topDocument.axObjectCache()) 89 cache->childrenChanged(cache->getOrCreate(&topDocument)); 90 } 91 92 inline HTMLDialogElement::HTMLDialogElement(Document& document) 93 : HTMLElement(dialogTag, document) 94 , m_centeringMode(NotCentered) 95 , m_centeredPosition(0) 96 , m_returnValue("") 97 { 98 ScriptWrappable::init(this); 99 } 100 101 DEFINE_NODE_FACTORY(HTMLDialogElement) 102 103 void HTMLDialogElement::close(const String& returnValue, ExceptionState& exceptionState) 104 { 105 if (!fastHasAttribute(openAttr)) { 106 exceptionState.throwDOMException(InvalidStateError, "The element does not have an 'open' attribute, and therefore cannot be closed."); 107 return; 108 } 109 closeDialog(returnValue); 110 } 111 112 void HTMLDialogElement::closeDialog(const String& returnValue) 113 { 114 if (!fastHasAttribute(openAttr)) 115 return; 116 setBooleanAttribute(openAttr, false); 117 118 HTMLDialogElement* activeModalDialog = document().activeModalDialog(); 119 document().removeFromTopLayer(this); 120 if (activeModalDialog == this) 121 inertSubtreesChanged(document()); 122 123 if (!returnValue.isNull()) 124 m_returnValue = returnValue; 125 126 dispatchScopedEvent(Event::create(EventTypeNames::close)); 127 } 128 129 void HTMLDialogElement::forceLayoutForCentering() 130 { 131 m_centeringMode = NeedsCentering; 132 document().updateLayoutIgnorePendingStylesheets(); 133 if (m_centeringMode == NeedsCentering) 134 setNotCentered(); 135 } 136 137 void HTMLDialogElement::show() 138 { 139 if (fastHasAttribute(openAttr)) 140 return; 141 setBooleanAttribute(openAttr, true); 142 } 143 144 void HTMLDialogElement::showModal(ExceptionState& exceptionState) 145 { 146 if (fastHasAttribute(openAttr)) { 147 exceptionState.throwDOMException(InvalidStateError, "The element already has an 'open' attribute, and therefore cannot be opened modally."); 148 return; 149 } 150 if (!inDocument()) { 151 exceptionState.throwDOMException(InvalidStateError, "The element is not in a Document."); 152 return; 153 } 154 155 document().addToTopLayer(this); 156 setBooleanAttribute(openAttr, true); 157 158 // Throw away the AX cache first, so the subsequent steps don't have a chance of queuing up 159 // AX events on objects that would be invalidated when the cache is thrown away. 160 inertSubtreesChanged(document()); 161 162 forceLayoutForCentering(); 163 setFocusForModalDialog(this); 164 } 165 166 void HTMLDialogElement::removedFrom(ContainerNode* insertionPoint) 167 { 168 HTMLElement::removedFrom(insertionPoint); 169 setNotCentered(); 170 // FIXME: We should call inertSubtreesChanged() here. 171 } 172 173 void HTMLDialogElement::setCentered(LayoutUnit centeredPosition) 174 { 175 ASSERT(m_centeringMode == NeedsCentering); 176 m_centeredPosition = centeredPosition; 177 m_centeringMode = Centered; 178 } 179 180 void HTMLDialogElement::setNotCentered() 181 { 182 m_centeringMode = NotCentered; 183 } 184 185 bool HTMLDialogElement::isPresentationAttribute(const QualifiedName& name) const 186 { 187 // FIXME: Workaround for <https://bugs.webkit.org/show_bug.cgi?id=91058>: modifying an attribute for which there is an attribute selector 188 // in html.css sometimes does not trigger a style recalc. 189 if (name == openAttr) 190 return true; 191 192 return HTMLElement::isPresentationAttribute(name); 193 } 194 195 void HTMLDialogElement::defaultEventHandler(Event* event) 196 { 197 if (event->type() == EventTypeNames::cancel) { 198 closeDialog(); 199 event->setDefaultHandled(); 200 return; 201 } 202 HTMLElement::defaultEventHandler(event); 203 } 204 205 } // namespace WebCore 206