• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006, 2008, 2009 Apple 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include "config.h"
27 #include "DeleteButtonController.h"
28 
29 #include "CachedImage.h"
30 #include "CSSMutableStyleDeclaration.h"
31 #include "CSSPrimitiveValue.h"
32 #include "CSSPropertyNames.h"
33 #include "CSSValueKeywords.h"
34 #include "DeleteButton.h"
35 #include "Document.h"
36 #include "Editor.h"
37 #include "Frame.h"
38 #include "htmlediting.h"
39 #include "HTMLDivElement.h"
40 #include "HTMLNames.h"
41 #include "Image.h"
42 #include "Node.h"
43 #include "Range.h"
44 #include "RemoveNodeCommand.h"
45 #include "RenderBox.h"
46 #include "SelectionController.h"
47 
48 namespace WebCore {
49 
50 using namespace HTMLNames;
51 
52 const char* const DeleteButtonController::containerElementIdentifier = "WebKit-Editing-Delete-Container";
53 const char* const DeleteButtonController::buttonElementIdentifier = "WebKit-Editing-Delete-Button";
54 const char* const DeleteButtonController::outlineElementIdentifier = "WebKit-Editing-Delete-Outline";
55 
DeleteButtonController(Frame * frame)56 DeleteButtonController::DeleteButtonController(Frame* frame)
57     : m_frame(frame)
58     , m_wasStaticPositioned(false)
59     , m_wasAutoZIndex(false)
60     , m_disableStack(0)
61 {
62 }
63 
isDeletableElement(const Node * node)64 static bool isDeletableElement(const Node* node)
65 {
66     if (!node || !node->isHTMLElement() || !node->inDocument() || !node->rendererIsEditable())
67         return false;
68 
69     // In general we want to only draw the UI around object of a certain area, but we still keep the min width/height to
70     // make sure we don't end up with very thin or very short elements getting the UI.
71     const int minimumArea = 2500;
72     const int minimumWidth = 48;
73     const int minimumHeight = 16;
74     const unsigned minimumVisibleBorders = 1;
75 
76     RenderObject* renderer = node->renderer();
77     if (!renderer || !renderer->isBox())
78         return false;
79 
80     // Disallow the body element since it isn't practical to delete, and the deletion UI would be clipped.
81     if (node->hasTagName(bodyTag))
82         return false;
83 
84     // Disallow elements with any overflow clip, since the deletion UI would be clipped as well. <rdar://problem/6840161>
85     if (renderer->hasOverflowClip())
86         return false;
87 
88     // Disallow Mail blockquotes since the deletion UI would get in the way of editing for these.
89     if (isMailBlockquote(node))
90         return false;
91 
92     RenderBox* box = toRenderBox(renderer);
93     IntRect borderBoundingBox = box->borderBoundingBox();
94     if (borderBoundingBox.width() < minimumWidth || borderBoundingBox.height() < minimumHeight)
95         return false;
96 
97     if ((borderBoundingBox.width() * borderBoundingBox.height()) < minimumArea)
98         return false;
99 
100     if (renderer->isTable())
101         return true;
102 
103     if (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(iframeTag))
104         return true;
105 
106     if (renderer->isPositioned())
107         return true;
108 
109     if (renderer->isRenderBlock() && !renderer->isTableCell()) {
110         RenderStyle* style = renderer->style();
111         if (!style)
112             return false;
113 
114         // Allow blocks that have background images
115         if (style->hasBackgroundImage()) {
116             for (const FillLayer* background = style->backgroundLayers(); background; background = background->next()) {
117                 if (background->image() && background->image()->canRender(1))
118                     return true;
119             }
120         }
121 
122         // Allow blocks with a minimum number of non-transparent borders
123         unsigned visibleBorders = style->borderTop().isVisible() + style->borderBottom().isVisible() + style->borderLeft().isVisible() + style->borderRight().isVisible();
124         if (visibleBorders >= minimumVisibleBorders)
125             return true;
126 
127         // Allow blocks that have a different background from it's parent
128         ContainerNode* parentNode = node->parentNode();
129         if (!parentNode)
130             return false;
131 
132         RenderObject* parentRenderer = parentNode->renderer();
133         if (!parentRenderer)
134             return false;
135 
136         RenderStyle* parentStyle = parentRenderer->style();
137         if (!parentStyle)
138             return false;
139 
140         if (renderer->hasBackground() && (!parentRenderer->hasBackground() || style->visitedDependentColor(CSSPropertyBackgroundColor) != parentStyle->visitedDependentColor(CSSPropertyBackgroundColor)))
141             return true;
142     }
143 
144     return false;
145 }
146 
enclosingDeletableElement(const VisibleSelection & selection)147 static HTMLElement* enclosingDeletableElement(const VisibleSelection& selection)
148 {
149     if (!selection.isContentEditable())
150         return 0;
151 
152     RefPtr<Range> range = selection.toNormalizedRange();
153     if (!range)
154         return 0;
155 
156     ExceptionCode ec = 0;
157     Node* container = range->commonAncestorContainer(ec);
158     ASSERT(container);
159     ASSERT(ec == 0);
160 
161     // The enclosingNodeOfType function only works on nodes that are editable
162     // (which is strange, given its name).
163     if (!container->rendererIsEditable())
164         return 0;
165 
166     Node* element = enclosingNodeOfType(firstPositionInNode(container), &isDeletableElement);
167     return element && element->isHTMLElement() ? toHTMLElement(element) : 0;
168 }
169 
respondToChangedSelection(const VisibleSelection & oldSelection)170 void DeleteButtonController::respondToChangedSelection(const VisibleSelection& oldSelection)
171 {
172     if (!enabled())
173         return;
174 
175     HTMLElement* oldElement = enclosingDeletableElement(oldSelection);
176     HTMLElement* newElement = enclosingDeletableElement(m_frame->selection()->selection());
177     if (oldElement == newElement)
178         return;
179 
180     // If the base is inside a deletable element, give the element a delete widget.
181     if (newElement)
182         show(newElement);
183     else
184         hide();
185 }
186 
createDeletionUI()187 void DeleteButtonController::createDeletionUI()
188 {
189     RefPtr<HTMLDivElement> container = HTMLDivElement::create(m_target->document());
190     container->setIdAttribute(containerElementIdentifier);
191 
192     CSSMutableStyleDeclaration* style = container->getInlineStyleDecl();
193     style->setProperty(CSSPropertyWebkitUserDrag, CSSValueNone);
194     style->setProperty(CSSPropertyWebkitUserSelect, CSSValueNone);
195     style->setProperty(CSSPropertyWebkitUserModify, CSSValueReadOnly);
196     style->setProperty(CSSPropertyVisibility, CSSValueHidden);
197     style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
198     style->setProperty(CSSPropertyCursor, CSSValueDefault);
199     style->setProperty(CSSPropertyTop, "0");
200     style->setProperty(CSSPropertyRight, "0");
201     style->setProperty(CSSPropertyBottom, "0");
202     style->setProperty(CSSPropertyLeft, "0");
203 
204     RefPtr<HTMLDivElement> outline = HTMLDivElement::create(m_target->document());
205     outline->setIdAttribute(outlineElementIdentifier);
206 
207     const int borderWidth = 4;
208     const int borderRadius = 6;
209 
210     style = outline->getInlineStyleDecl();
211     style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
212     style->setProperty(CSSPropertyZIndex, String::number(-1000000));
213     style->setProperty(CSSPropertyTop, String::number(-borderWidth - m_target->renderBox()->borderTop()) + "px");
214     style->setProperty(CSSPropertyRight, String::number(-borderWidth - m_target->renderBox()->borderRight()) + "px");
215     style->setProperty(CSSPropertyBottom, String::number(-borderWidth - m_target->renderBox()->borderBottom()) + "px");
216     style->setProperty(CSSPropertyLeft, String::number(-borderWidth - m_target->renderBox()->borderLeft()) + "px");
217     style->setProperty(CSSPropertyBorder, String::number(borderWidth) + "px solid rgba(0, 0, 0, 0.6)");
218     style->setProperty(CSSPropertyWebkitBorderRadius, String::number(borderRadius) + "px");
219     style->setProperty(CSSPropertyVisibility, CSSValueVisible);
220 
221     ExceptionCode ec = 0;
222     container->appendChild(outline.get(), ec);
223     ASSERT(ec == 0);
224     if (ec)
225         return;
226 
227     RefPtr<DeleteButton> button = DeleteButton::create(m_target->document());
228     button->setIdAttribute(buttonElementIdentifier);
229 
230     const int buttonWidth = 30;
231     const int buttonHeight = 30;
232     const int buttonBottomShadowOffset = 2;
233 
234     style = button->getInlineStyleDecl();
235     style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
236     style->setProperty(CSSPropertyZIndex, String::number(1000000));
237     style->setProperty(CSSPropertyTop, String::number((-buttonHeight / 2) - m_target->renderBox()->borderTop() - (borderWidth / 2) + buttonBottomShadowOffset) + "px");
238     style->setProperty(CSSPropertyLeft, String::number((-buttonWidth / 2) - m_target->renderBox()->borderLeft() - (borderWidth / 2)) + "px");
239     style->setProperty(CSSPropertyWidth, String::number(buttonWidth) + "px");
240     style->setProperty(CSSPropertyHeight, String::number(buttonHeight) + "px");
241     style->setProperty(CSSPropertyVisibility, CSSValueVisible);
242 
243     RefPtr<Image> buttonImage = Image::loadPlatformResource("deleteButton");
244     if (buttonImage->isNull())
245         return;
246 
247     button->setCachedImage(new CachedImage(buttonImage.get()));
248 
249     container->appendChild(button.get(), ec);
250     ASSERT(ec == 0);
251     if (ec)
252         return;
253 
254     m_containerElement = container.release();
255     m_outlineElement = outline.release();
256     m_buttonElement = button.release();
257 }
258 
show(HTMLElement * element)259 void DeleteButtonController::show(HTMLElement* element)
260 {
261     hide();
262 
263     if (!enabled() || !element || !element->inDocument() || !isDeletableElement(element))
264         return;
265 
266     if (!m_frame->editor()->shouldShowDeleteInterface(toHTMLElement(element)))
267         return;
268 
269     // we rely on the renderer having current information, so we should update the layout if needed
270     m_frame->document()->updateLayoutIgnorePendingStylesheets();
271 
272     m_target = element;
273 
274     if (!m_containerElement) {
275         createDeletionUI();
276         if (!m_containerElement) {
277             hide();
278             return;
279         }
280     }
281 
282     ExceptionCode ec = 0;
283     m_target->appendChild(m_containerElement.get(), ec);
284     ASSERT(ec == 0);
285     if (ec) {
286         hide();
287         return;
288     }
289 
290     if (m_target->renderer()->style()->position() == StaticPosition) {
291         m_target->getInlineStyleDecl()->setProperty(CSSPropertyPosition, CSSValueRelative);
292         m_wasStaticPositioned = true;
293     }
294 
295     if (m_target->renderer()->style()->hasAutoZIndex()) {
296         m_target->getInlineStyleDecl()->setProperty(CSSPropertyZIndex, "0");
297         m_wasAutoZIndex = true;
298     }
299 }
300 
hide()301 void DeleteButtonController::hide()
302 {
303     m_outlineElement = 0;
304     m_buttonElement = 0;
305 
306     ExceptionCode ec = 0;
307     if (m_containerElement && m_containerElement->parentNode())
308         m_containerElement->parentNode()->removeChild(m_containerElement.get(), ec);
309 
310     if (m_target) {
311         if (m_wasStaticPositioned)
312             m_target->getInlineStyleDecl()->setProperty(CSSPropertyPosition, CSSValueStatic);
313         if (m_wasAutoZIndex)
314             m_target->getInlineStyleDecl()->setProperty(CSSPropertyZIndex, CSSValueAuto);
315     }
316 
317     m_wasStaticPositioned = false;
318     m_wasAutoZIndex = false;
319 }
320 
enable()321 void DeleteButtonController::enable()
322 {
323     ASSERT(m_disableStack > 0);
324     if (m_disableStack > 0)
325         m_disableStack--;
326     if (enabled()) {
327         // Determining if the element is deletable currently depends on style
328         // because whether something is editable depends on style, so we need
329         // to recalculate style before calling enclosingDeletableElement.
330         m_frame->document()->updateStyleIfNeeded();
331         show(enclosingDeletableElement(m_frame->selection()->selection()));
332     }
333 }
334 
disable()335 void DeleteButtonController::disable()
336 {
337     if (enabled())
338         hide();
339     m_disableStack++;
340 }
341 
deleteTarget()342 void DeleteButtonController::deleteTarget()
343 {
344     if (!enabled() || !m_target)
345         return;
346 
347     RefPtr<Node> element = m_target;
348     hide();
349 
350     // Because the deletion UI only appears when the selection is entirely
351     // within the target, we unconditionally update the selection to be
352     // a caret where the target had been.
353     Position pos = positionInParentBeforeNode(element.get());
354     applyCommand(RemoveNodeCommand::create(element.release()));
355     m_frame->selection()->setSelection(VisiblePosition(pos));
356 }
357 
358 } // namespace WebCore
359