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 (node->hasTagName(dialogTag)) 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 control->setAutofocused(); 61 return; 62 } 63 } 64 if (!focusableDescendant && element->isFocusable()) 65 focusableDescendant = element; 66 } 67 68 if (focusableDescendant) { 69 focusableDescendant->focus(); 70 return; 71 } 72 73 if (dialog->isFocusable()) { 74 dialog->focus(); 75 return; 76 } 77 78 dialog->document().setFocusedElement(0); 79 } 80 81 static void inertSubtreesChanged(Document& document) 82 { 83 // When a modal dialog opens or closes, nodes all over the accessibility 84 // tree can change inertness which means they must be added or removed from 85 // the tree. The most foolproof way is to clear the entire tree and rebuild 86 // it, though a more clever way is probably possible. 87 Document* topDocument = document.topDocument(); 88 topDocument->clearAXObjectCache(); 89 if (AXObjectCache* cache = topDocument->axObjectCache()) 90 cache->childrenChanged(cache->getOrCreate(topDocument)); 91 } 92 93 HTMLDialogElement::HTMLDialogElement(Document& document) 94 : HTMLElement(dialogTag, document) 95 , m_centeringMode(Uninitialized) 96 , m_centeredPosition(0) 97 , m_returnValue("") 98 { 99 ScriptWrappable::init(this); 100 } 101 102 PassRefPtr<HTMLDialogElement> HTMLDialogElement::create(Document& document) 103 { 104 return adoptRef(new HTMLDialogElement(document)); 105 } 106 107 void HTMLDialogElement::close(const String& returnValue, ExceptionState& exceptionState) 108 { 109 if (!fastHasAttribute(openAttr)) { 110 exceptionState.throwDOMException(InvalidStateError, "The element does not have an 'open' attribute, and therefore cannot be closed."); 111 return; 112 } 113 closeDialog(returnValue); 114 } 115 116 void HTMLDialogElement::closeDialog(const String& returnValue) 117 { 118 if (!fastHasAttribute(openAttr)) 119 return; 120 setBooleanAttribute(openAttr, false); 121 122 HTMLDialogElement* activeModalDialog = document().activeModalDialog(); 123 document().removeFromTopLayer(this); 124 if (activeModalDialog == this) 125 inertSubtreesChanged(document()); 126 127 if (!returnValue.isNull()) 128 m_returnValue = returnValue; 129 130 dispatchScopedEvent(Event::create(EventTypeNames::close)); 131 } 132 133 void HTMLDialogElement::forceLayoutForCentering() 134 { 135 m_centeringMode = Uninitialized; 136 document().updateLayoutIgnorePendingStylesheets(); 137 if (m_centeringMode == Uninitialized) 138 m_centeringMode = NotCentered; 139 } 140 141 void HTMLDialogElement::show() 142 { 143 if (fastHasAttribute(openAttr)) 144 return; 145 setBooleanAttribute(openAttr, true); 146 forceLayoutForCentering(); 147 } 148 149 void HTMLDialogElement::showModal(ExceptionState& exceptionState) 150 { 151 if (fastHasAttribute(openAttr)) { 152 exceptionState.throwDOMException(InvalidStateError, "The element already has an 'open' attribute, and therefore cannot be opened modally."); 153 return; 154 } 155 if (!inDocument()) { 156 exceptionState.throwDOMException(InvalidStateError, "The element is not in a Document."); 157 return; 158 } 159 160 document().addToTopLayer(this); 161 setBooleanAttribute(openAttr, true); 162 163 // Throw away the AX cache first, so the subsequent steps don't have a chance of queuing up 164 // AX events on objects that would be invalidated when the cache is thrown away. 165 inertSubtreesChanged(document()); 166 167 forceLayoutForCentering(); 168 setFocusForModalDialog(this); 169 } 170 171 void HTMLDialogElement::setCentered(LayoutUnit centeredPosition) 172 { 173 ASSERT(m_centeringMode == Uninitialized); 174 m_centeredPosition = centeredPosition; 175 m_centeringMode = Centered; 176 } 177 178 void HTMLDialogElement::setNotCentered() 179 { 180 ASSERT(m_centeringMode == Uninitialized); 181 m_centeringMode = NotCentered; 182 } 183 184 bool HTMLDialogElement::isPresentationAttribute(const QualifiedName& name) const 185 { 186 // FIXME: Workaround for <https://bugs.webkit.org/show_bug.cgi?id=91058>: modifying an attribute for which there is an attribute selector 187 // in html.css sometimes does not trigger a style recalc. 188 if (name == openAttr) 189 return true; 190 191 return HTMLElement::isPresentationAttribute(name); 192 } 193 194 void HTMLDialogElement::defaultEventHandler(Event* event) 195 { 196 if (event->type() == EventTypeNames::cancel) { 197 closeDialog(); 198 event->setDefaultHandled(); 199 return; 200 } 201 HTMLElement::defaultEventHandler(event); 202 } 203 204 bool HTMLDialogElement::shouldBeReparentedUnderRenderView(const RenderStyle* style) const 205 { 206 if (style && style->position() == AbsolutePosition) 207 return true; 208 return Element::shouldBeReparentedUnderRenderView(style); 209 } 210 211 } // namespace WebCore 212