2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
32 #include "core/frame/SmartClip.h"
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"
46 static IntRect applyScaleWithoutCollapsingToZero(const IntRect& rect, float scale)
48 IntRect result = rect;
50 if (rect.width() > 0 && !result.width())
52 if (rect.height() > 0 && !result.height())
57 static Node* nodeInsideFrame(Node* node)
59 if (node->isFrameOwnerElement())
60 return toHTMLFrameOwnerElement(node)->contentDocument();
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()
76 const UChar fieldSeparator = 0xFFFE;
77 const UChar rowSeparator = 0xFFFF;
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();
93 SmartClip::SmartClip(PassRefPtr<LocalFrame> frame)
98 SmartClipData SmartClip::dataForRect(const IntRect& cropRect)
100 IntRect resizedCropRect = applyScaleWithoutCollapsingToZero(cropRect, 1 / pageScaleFactor());
102 Node* bestNode = findBestOverlappingNode(m_frame->document(), resizedCropRect);
104 return SmartClipData();
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;
112 Vector<Node*> hitNodes;
113 collectOverlappingChildNodes(bestNode, resizedCropRect, hitNodes);
115 if (hitNodes.isEmpty() || hitNodes.size() == bestNode->countChildren()) {
117 hitNodes.append(bestNode);
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());
128 return SmartClipData(bestNode, convertRectToWindow(unitedRects), collectedText.toString());
131 float SmartClip::pageScaleFactor()
133 return m_frame->page()->pageScaleFactor();
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)
145 IntRect minNodeRect = minNode->pixelSnappedBoundingBox();
146 IntRect newNodeRect = newNode->pixelSnappedBoundingBox();
148 Node* parentMinNode = minNode->parentNode();
149 Node* parentNewNode = newNode->parentNode();
151 if (minNodeRect.contains(newNodeRect)) {
152 if (parentMinNode && parentNewNode && parentNewNode->parentNode() == parentMinNode)
153 return parentMinNode;
157 if (newNodeRect.contains(minNodeRect)) {
158 if (parentMinNode && parentNewNode && parentMinNode->parentNode() == parentNewNode)
159 return parentNewNode;
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;
169 if (node->renderer()) {
170 IntRect nodeRect = node->pixelSnappedBoundingBox();
171 if (nodeRect.contains(newNodeRect)) {
175 node = node->parentNode();
181 Node* SmartClip::findBestOverlappingNode(Node* rootNode, const IntRect& cropRect)
186 IntRect resizedCropRect = rootNode->document().view()->windowToContents(cropRect);
188 Node* node = rootNode;
192 IntRect nodeRect = node->pixelSnappedBoundingBox();
194 if (node->isElementNode() && equalIgnoringCase(toElement(node)->fastGetAttribute(HTMLNames::aria_hiddenAttr), "true")) {
195 node = NodeTraversal::nextSkippingChildren(*node, rootNode);
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);
208 node = NodeTraversal::nextSkippingChildren(*node, rootNode);
213 node = NodeTraversal::next(*node, rootNode);
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)
225 // Apparently we're only interested in background images on spans and divs.
226 if (!isHTMLSpanElement(*node) && !isHTMLDivElement(*node))
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()))
240 void SmartClip::collectOverlappingChildNodes(Node* parentNode, const IntRect& cropRect, Vector<Node*>& hitNodes)
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);
252 IntRect SmartClip::convertRectToWindow(const IntRect& nodeRect)
254 IntRect result = m_frame->document()->view()->contentsToWindow(nodeRect);
255 result.scale(pageScaleFactor());
259 String SmartClip::extractTextFromNode(Node* node)
261 // Science has proven that no text nodes are ever positioned at y == -99999.
262 int prevYPos = -99999;
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)
270 if (Node* nodeFromFrame = nodeInsideFrame(currentNode))
271 result.append(extractTextFromNode(nodeFromFrame));
273 IntRect nodeRect = currentNode->pixelSnappedBoundingBox();
274 if (currentNode->renderer() && !nodeRect.isEmpty()) {
275 if (currentNode->isTextNode()) {
276 String nodeValue = currentNode->nodeValue();
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")
283 if (nodeRect.y() != prevYPos) {
284 prevYPos = nodeRect.y();
288 result.append(nodeValue);
293 return result.toString();
296 } // namespace WebCore