Upstream version 6.35.121.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / frame / SmartClip.cpp
1 /*
2  * Copyright (C) 2013 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 are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "core/frame/SmartClip.h"
33
34 #include "core/dom/ContainerNode.h"
35 #include "core/dom/Document.h"
36 #include "core/dom/NodeTraversal.h"
37 #include "core/frame/DOMWindow.h"
38 #include "core/frame/FrameView.h"
39 #include "core/html/HTMLFrameOwnerElement.h"
40 #include "core/page/Page.h"
41 #include "core/rendering/RenderObject.h"
42 #include "wtf/text/StringBuilder.h"
43
44 namespace WebCore {
45
46 static IntRect applyScaleWithoutCollapsingToZero(const IntRect& rect, float scale)
47 {
48     IntRect result = rect;
49     result.scale(scale);
50     if (rect.width() > 0 && !result.width())
51         result.setWidth(1);
52     if (rect.height() > 0 && !result.height())
53         result.setHeight(1);
54     return result;
55 }
56
57 static Node* nodeInsideFrame(Node* node)
58 {
59     if (node->isFrameOwnerElement())
60         return toHTMLFrameOwnerElement(node)->contentDocument();
61     return 0;
62 }
63
64 // FIXME: SmartClipData is eventually returned via
65 // SLookSmartClip.DataExtractionListener:
66 // http://img-developer.samsung.com/onlinedocs/sms/com/samsung/android/sdk/look/...
67 // however the original author of this change chose to use a string-serialization
68 // format (presumably to make IPC easy?).
69 // If we're going to use this as a Pickle format, we should at least have the
70 // read/write code in one place!
71 String SmartClipData::toString()
72 {
73     if (!m_node)
74         return emptyString();
75
76     const UChar fieldSeparator = 0xFFFE;
77     const UChar rowSeparator = 0xFFFF;
78
79     StringBuilder result;
80     result.append(String::number(m_rect.x()));
81     result.append(fieldSeparator);
82     result.append(String::number(m_rect.y()));
83     result.append(fieldSeparator);
84     result.append(String::number(m_rect.width()));
85     result.append(fieldSeparator);
86     result.append(String::number(m_rect.height()));
87     result.append(fieldSeparator);
88     result.append(m_string);
89     result.append(rowSeparator);
90     return result.toString();
91 }
92
93 SmartClip::SmartClip(PassRefPtr<LocalFrame> frame)
94     : m_frame(frame)
95 {
96 }
97
98 SmartClipData SmartClip::dataForRect(const IntRect& cropRect)
99 {
100     IntRect resizedCropRect = applyScaleWithoutCollapsingToZero(cropRect, 1 / pageScaleFactor());
101
102     Node* bestNode = findBestOverlappingNode(m_frame->document(), resizedCropRect);
103     if (!bestNode)
104         return SmartClipData();
105
106     if (Node* nodeFromFrame = nodeInsideFrame(bestNode)) {
107         // FIXME: This code only hit-tests a single iframe. It seems like we ought support nested frames.
108         if (Node* bestNodeInFrame = findBestOverlappingNode(nodeFromFrame, resizedCropRect))
109             bestNode = bestNodeInFrame;
110     }
111
112     Vector<Node*> hitNodes;
113     collectOverlappingChildNodes(bestNode, resizedCropRect, hitNodes);
114
115     if (hitNodes.isEmpty() || hitNodes.size() == bestNode->countChildren()) {
116         hitNodes.clear();
117         hitNodes.append(bestNode);
118     }
119
120     // Unite won't work with the empty rect, so we initialize to the first rect.
121     IntRect unitedRects = hitNodes[0]->pixelSnappedBoundingBox();
122     StringBuilder collectedText;
123     for (size_t i = 0; i < hitNodes.size(); ++i) {
124         collectedText.append(extractTextFromNode(hitNodes[i]));
125         unitedRects.unite(hitNodes[i]->pixelSnappedBoundingBox());
126     }
127
128     return SmartClipData(bestNode, convertRectToWindow(unitedRects), collectedText.toString());
129 }
130
131 float SmartClip::pageScaleFactor()
132 {
133     return m_frame->page()->pageScaleFactor();
134 }
135
136 // This function is a bit of a mystery. If you understand what it does, please
137 // consider adding a more descriptive name.
138 Node* SmartClip::minNodeContainsNodes(Node* minNode, Node* newNode)
139 {
140     if (!newNode)
141         return minNode;
142     if (!minNode)
143         return newNode;
144
145     IntRect minNodeRect = minNode->pixelSnappedBoundingBox();
146     IntRect newNodeRect = newNode->pixelSnappedBoundingBox();
147
148     Node* parentMinNode = minNode->parentNode();
149     Node* parentNewNode = newNode->parentNode();
150
151     if (minNodeRect.contains(newNodeRect)) {
152         if (parentMinNode && parentNewNode && parentNewNode->parentNode() == parentMinNode)
153             return parentMinNode;
154         return minNode;
155     }
156
157     if (newNodeRect.contains(minNodeRect)) {
158         if (parentMinNode && parentNewNode && parentMinNode->parentNode() == parentNewNode)
159             return parentNewNode;
160         return newNode;
161     }
162
163     // This loop appears to find the nearest ancestor of minNode (in DOM order)
164     // that contains the newNodeRect. It's very unclear to me why that's an
165     // interesting node to find. Presumably this loop will often just return
166     // the documentElement.
167     Node* node = minNode;
168     while (node) {
169         if (node->renderer()) {
170             IntRect nodeRect = node->pixelSnappedBoundingBox();
171             if (nodeRect.contains(newNodeRect)) {
172                 return node;
173             }
174         }
175         node = node->parentNode();
176     }
177
178     return 0;
179 }
180
181 Node* SmartClip::findBestOverlappingNode(Node* rootNode, const IntRect& cropRect)
182 {
183     if (!rootNode)
184         return 0;
185
186     IntRect resizedCropRect = rootNode->document().view()->windowToContents(cropRect);
187
188     Node* node = rootNode;
189     Node* minNode = 0;
190
191     while (node) {
192         IntRect nodeRect = node->pixelSnappedBoundingBox();
193
194         if (node->isElementNode() && equalIgnoringCase(toElement(node)->fastGetAttribute(HTMLNames::aria_hiddenAttr), "true")) {
195             node = NodeTraversal::nextSkippingChildren(*node, rootNode);
196             continue;
197         }
198
199         RenderObject* renderer = node->renderer();
200         if (renderer && !nodeRect.isEmpty()) {
201             if (renderer->isText()
202                 || renderer->isRenderImage()
203                 || node->isFrameOwnerElement()
204                 || (renderer->style()->hasBackgroundImage() && !shouldSkipBackgroundImage(node))) {
205                 if (resizedCropRect.intersects(nodeRect)) {
206                     minNode = minNodeContainsNodes(minNode, node);
207                 } else {
208                     node = NodeTraversal::nextSkippingChildren(*node, rootNode);
209                     continue;
210                 }
211             }
212         }
213         node = NodeTraversal::next(*node, rootNode);
214     }
215
216     return minNode;
217 }
218
219 // This function appears to heuristically guess whether to include a background
220 // image in the smart clip. It seems to want to include sprites created from
221 // CSS background images but to skip actual backgrounds.
222 bool SmartClip::shouldSkipBackgroundImage(Node* node)
223 {
224     ASSERT(node);
225     // Apparently we're only interested in background images on spans and divs.
226     if (!isHTMLSpanElement(*node) && !isHTMLDivElement(*node))
227         return true;
228
229     // This check actually makes a bit of sense. If you're going to sprite an
230     // image out of a CSS background, you're probably going to specify a height
231     // or a width. On the other hand, if we've got a legit background image,
232     // it's very likely the height or the width will be set to auto.
233     RenderObject* renderer = node->renderer();
234     if (renderer && (renderer->style()->logicalHeight().isAuto() || renderer->style()->logicalWidth().isAuto()))
235         return true;
236
237     return false;
238 }
239
240 void SmartClip::collectOverlappingChildNodes(Node* parentNode, const IntRect& cropRect, Vector<Node*>& hitNodes)
241 {
242     if (!parentNode)
243         return;
244     IntRect resizedCropRect = parentNode->document().view()->windowToContents(cropRect);
245     for (Node* child = parentNode->firstChild(); child; child = child->nextSibling()) {
246         IntRect childRect = child->pixelSnappedBoundingBox();
247         if (resizedCropRect.intersects(childRect))
248             hitNodes.append(child);
249     }
250 }
251
252 IntRect SmartClip::convertRectToWindow(const IntRect& nodeRect)
253 {
254     IntRect result = m_frame->document()->view()->contentsToWindow(nodeRect);
255     result.scale(pageScaleFactor());
256     return result;
257 }
258
259 String SmartClip::extractTextFromNode(Node* node)
260 {
261     // Science has proven that no text nodes are ever positioned at y == -99999.
262     int prevYPos = -99999;
263
264     StringBuilder result;
265     for (Node* currentNode = node; currentNode; currentNode = NodeTraversal::next(*currentNode, node)) {
266         RenderStyle* style = currentNode->computedStyle();
267         if (style && style->userSelect() == SELECT_NONE)
268             continue;
269
270         if (Node* nodeFromFrame = nodeInsideFrame(currentNode))
271             result.append(extractTextFromNode(nodeFromFrame));
272
273         IntRect nodeRect = currentNode->pixelSnappedBoundingBox();
274         if (currentNode->renderer() && !nodeRect.isEmpty()) {
275             if (currentNode->isTextNode()) {
276                 String nodeValue = currentNode->nodeValue();
277
278                 // It's unclear why we blacklist solitary "\n" node values.
279                 // Maybe we're trying to ignore <br> tags somehow?
280                 if (nodeValue == "\n")
281                     nodeValue = "";
282
283                 if (nodeRect.y() != prevYPos) {
284                     prevYPos = nodeRect.y();
285                     result.append('\n');
286                 }
287
288                 result.append(nodeValue);
289             }
290         }
291     }
292
293     return result.toString();
294 }
295
296 } // namespace WebCore