Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / page / PageSerializer.cpp
1 /*
2  * Copyright (C) 2011 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/page/PageSerializer.h"
33
34 #include "HTMLNames.h"
35 #include "core/css/CSSFontFaceRule.h"
36 #include "core/css/CSSFontFaceSrcValue.h"
37 #include "core/css/CSSImageValue.h"
38 #include "core/css/CSSImportRule.h"
39 #include "core/css/CSSStyleDeclaration.h"
40 #include "core/css/CSSStyleRule.h"
41 #include "core/css/CSSValueList.h"
42 #include "core/css/StylePropertySet.h"
43 #include "core/css/StyleRule.h"
44 #include "core/css/StyleSheetContents.h"
45 #include "core/dom/Document.h"
46 #include "core/dom/Element.h"
47 #include "core/dom/Text.h"
48 #include "core/editing/MarkupAccumulator.h"
49 #include "core/fetch/FontResource.h"
50 #include "core/fetch/ImageResource.h"
51 #include "core/frame/Frame.h"
52 #include "core/html/HTMLFrameOwnerElement.h"
53 #include "core/html/HTMLImageElement.h"
54 #include "core/html/HTMLInputElement.h"
55 #include "core/html/HTMLLinkElement.h"
56 #include "core/html/HTMLStyleElement.h"
57 #include "core/html/parser/HTMLParserIdioms.h"
58 #include "core/page/Page.h"
59 #include "core/rendering/RenderImage.h"
60 #include "core/rendering/style/StyleFetchedImage.h"
61 #include "core/rendering/style/StyleImage.h"
62 #include "platform/SerializedResource.h"
63 #include "platform/graphics/Image.h"
64 #include "wtf/text/CString.h"
65 #include "wtf/text/StringBuilder.h"
66 #include "wtf/text/TextEncoding.h"
67 #include "wtf/text/WTFString.h"
68
69 namespace WebCore {
70
71 static bool isCharsetSpecifyingNode(const Node& node)
72 {
73     if (!node.isHTMLElement())
74         return false;
75
76     const HTMLElement& element = toHTMLElement(node);
77     if (!element.hasTagName(HTMLNames::metaTag))
78         return false;
79     HTMLAttributeList attributes;
80     if (element.hasAttributes()) {
81         for (unsigned i = 0; i < element.attributeCount(); ++i) {
82             const Attribute* attribute = element.attributeItem(i);
83             // FIXME: We should deal appropriately with the attribute if they have a namespace.
84             attributes.append(std::make_pair(attribute->name().localName(), attribute->value().string()));
85         }
86     }
87     WTF::TextEncoding textEncoding = encodingFromMetaAttributes(attributes);
88     return textEncoding.isValid();
89 }
90
91 static bool shouldIgnoreElement(const Element& element)
92 {
93     return element.hasTagName(HTMLNames::scriptTag) || element.hasTagName(HTMLNames::noscriptTag) || isCharsetSpecifyingNode(element);
94 }
95
96 static const QualifiedName& frameOwnerURLAttributeName(const HTMLFrameOwnerElement& frameOwner)
97 {
98     // FIXME: We should support all frame owners including applets.
99     return frameOwner.hasTagName(HTMLNames::objectTag) ? HTMLNames::dataAttr : HTMLNames::srcAttr;
100 }
101
102 class SerializerMarkupAccumulator FINAL : public MarkupAccumulator {
103 public:
104     SerializerMarkupAccumulator(PageSerializer*, const Document&, Vector<Node*>*);
105     virtual ~SerializerMarkupAccumulator();
106
107 protected:
108     virtual void appendText(StringBuilder& out, Text&) OVERRIDE;
109     virtual void appendElement(StringBuilder& out, Element&, Namespaces*) OVERRIDE;
110     virtual void appendCustomAttributes(StringBuilder& out, const Element&, Namespaces*) OVERRIDE;
111     virtual void appendEndTag(const Node&) OVERRIDE;
112
113 private:
114     PageSerializer* m_serializer;
115     const Document& m_document;
116 };
117
118 SerializerMarkupAccumulator::SerializerMarkupAccumulator(PageSerializer* serializer, const Document& document, Vector<Node*>* nodes)
119     : MarkupAccumulator(nodes, ResolveAllURLs)
120     , m_serializer(serializer)
121     , m_document(document)
122 {
123 }
124
125 SerializerMarkupAccumulator::~SerializerMarkupAccumulator()
126 {
127 }
128
129 void SerializerMarkupAccumulator::appendText(StringBuilder& out, Text& text)
130 {
131     Element* parent = text.parentElement();
132     if (parent && !shouldIgnoreElement(*parent))
133         MarkupAccumulator::appendText(out, text);
134 }
135
136 void SerializerMarkupAccumulator::appendElement(StringBuilder& out, Element& element, Namespaces* namespaces)
137 {
138     if (!shouldIgnoreElement(element))
139         MarkupAccumulator::appendElement(out, element, namespaces);
140
141     if (element.hasTagName(HTMLNames::headTag)) {
142         out.append("<meta charset=\"");
143         out.append(m_document.charset());
144         out.append("\">");
145     }
146
147     // FIXME: For object (plugins) tags and video tag we could replace them by an image of their current contents.
148 }
149
150 void SerializerMarkupAccumulator::appendCustomAttributes(StringBuilder& out, const Element& element, Namespaces* namespaces)
151 {
152     if (!element.isFrameOwnerElement())
153         return;
154
155     const HTMLFrameOwnerElement& frameOwner = toHTMLFrameOwnerElement(element);
156     Frame* frame = frameOwner.contentFrame();
157     if (!frame)
158         return;
159
160     KURL url = frame->document()->url();
161     if (url.isValid() && !url.isBlankURL())
162         return;
163
164     // We need to give a fake location to blank frames so they can be referenced by the serialized frame.
165     url = m_serializer->urlForBlankFrame(frame);
166     appendAttribute(out, element, Attribute(frameOwnerURLAttributeName(frameOwner), AtomicString(url.string())), namespaces);
167 }
168
169 void SerializerMarkupAccumulator::appendEndTag(const Node& node)
170 {
171     if (node.isElementNode() && !shouldIgnoreElement(toElement(node)))
172         MarkupAccumulator::appendEndTag(node);
173 }
174
175 PageSerializer::PageSerializer(Vector<SerializedResource>* resources)
176     : m_resources(resources)
177     , m_blankFrameCounter(0)
178 {
179 }
180
181 void PageSerializer::serialize(Page* page)
182 {
183     serializeFrame(page->mainFrame());
184 }
185
186 void PageSerializer::serializeFrame(Frame* frame)
187 {
188     ASSERT(frame->document());
189     Document& document = *frame->document();
190     KURL url = document.url();
191     if (!url.isValid() || url.isBlankURL()) {
192         // For blank frames we generate a fake URL so they can be referenced by their containing frame.
193         url = urlForBlankFrame(frame);
194     }
195
196     if (m_resourceURLs.contains(url)) {
197         // FIXME: We could have 2 frame with the same URL but which were dynamically changed and have now
198         // different content. So we should serialize both and somehow rename the frame src in the containing
199         // frame. Arg!
200         return;
201     }
202
203     WTF::TextEncoding textEncoding(document.charset());
204     if (!textEncoding.isValid()) {
205         // FIXME: iframes used as images trigger this. We should deal with them correctly.
206         return;
207     }
208
209     Vector<Node*> serializedNodes;
210     SerializerMarkupAccumulator accumulator(this, document, &serializedNodes);
211     String text = accumulator.serializeNodes(document, IncludeNode);
212     CString frameHTML = textEncoding.normalizeAndEncode(text, WTF::EntitiesForUnencodables);
213     m_resources->append(SerializedResource(url, document.suggestedMIMEType(), SharedBuffer::create(frameHTML.data(), frameHTML.length())));
214     m_resourceURLs.add(url);
215
216     for (Vector<Node*>::iterator iter = serializedNodes.begin(); iter != serializedNodes.end(); ++iter) {
217         ASSERT(*iter);
218         Node& node = **iter;
219         if (!node.isElementNode())
220             continue;
221
222         Element& element = toElement(node);
223         // We have to process in-line style as it might contain some resources (typically background images).
224         if (element.isStyledElement())
225             retrieveResourcesForProperties(element.inlineStyle(), document);
226
227         if (element.hasTagName(HTMLNames::imgTag)) {
228             HTMLImageElement& imageElement = toHTMLImageElement(element);
229             KURL url = document.completeURL(imageElement.getAttribute(HTMLNames::srcAttr));
230             ImageResource* cachedImage = imageElement.cachedImage();
231             addImageToResources(cachedImage, imageElement.renderer(), url);
232         } else if (element.hasTagName(HTMLNames::inputTag)) {
233             HTMLInputElement& inputElement = toHTMLInputElement(element);
234             if (inputElement.isImageButton() && inputElement.hasImageLoader()) {
235                 KURL url = inputElement.src();
236                 ImageResource* cachedImage = inputElement.imageLoader()->image();
237                 addImageToResources(cachedImage, inputElement.renderer(), url);
238             }
239         } else if (element.hasTagName(HTMLNames::linkTag)) {
240             HTMLLinkElement& linkElement = toHTMLLinkElement(element);
241             if (CSSStyleSheet* sheet = linkElement.sheet()) {
242                 KURL url = document.completeURL(linkElement.getAttribute(HTMLNames::hrefAttr));
243                 serializeCSSStyleSheet(sheet, url);
244                 ASSERT(m_resourceURLs.contains(url));
245             }
246         } else if (element.hasTagName(HTMLNames::styleTag)) {
247             HTMLStyleElement& styleElement = toHTMLStyleElement(element);
248             if (CSSStyleSheet* sheet = styleElement.sheet())
249                 serializeCSSStyleSheet(sheet, KURL());
250         }
251     }
252
253     for (Frame* childFrame = frame->tree().firstChild(); childFrame; childFrame = childFrame->tree().nextSibling())
254         serializeFrame(childFrame);
255 }
256
257 void PageSerializer::serializeCSSStyleSheet(CSSStyleSheet* styleSheet, const KURL& url)
258 {
259     StringBuilder cssText;
260     for (unsigned i = 0; i < styleSheet->length(); ++i) {
261         CSSRule* rule = styleSheet->item(i);
262         String itemText = rule->cssText();
263         if (!itemText.isEmpty()) {
264             cssText.append(itemText);
265             if (i < styleSheet->length() - 1)
266                 cssText.append("\n\n");
267         }
268         ASSERT(styleSheet->ownerDocument());
269         Document& document = *styleSheet->ownerDocument();
270         // Some rules have resources associated with them that we need to retrieve.
271         if (rule->type() == CSSRule::IMPORT_RULE) {
272             CSSImportRule* importRule = toCSSImportRule(rule);
273             KURL importURL = document.completeURL(importRule->href());
274             if (m_resourceURLs.contains(importURL))
275                 continue;
276             serializeCSSStyleSheet(importRule->styleSheet(), importURL);
277         } else if (rule->type() == CSSRule::FONT_FACE_RULE) {
278             retrieveResourcesForProperties(toCSSFontFaceRule(rule)->styleRule()->properties(), document);
279         } else if (rule->type() == CSSRule::STYLE_RULE) {
280             retrieveResourcesForProperties(toCSSStyleRule(rule)->styleRule()->properties(), document);
281         }
282     }
283
284     if (url.isValid() && !m_resourceURLs.contains(url)) {
285         // FIXME: We should check whether a charset has been specified and if none was found add one.
286         WTF::TextEncoding textEncoding(styleSheet->contents()->charset());
287         ASSERT(textEncoding.isValid());
288         String textString = cssText.toString();
289         CString text = textEncoding.normalizeAndEncode(textString, WTF::EntitiesForUnencodables);
290         m_resources->append(SerializedResource(url, String("text/css"), SharedBuffer::create(text.data(), text.length())));
291         m_resourceURLs.add(url);
292     }
293 }
294
295 bool PageSerializer::shouldAddURL(const KURL& url)
296 {
297     return url.isValid() && !m_resourceURLs.contains(url) && !url.protocolIsData();
298 }
299
300 void PageSerializer::addToResources(Resource* resource, PassRefPtr<SharedBuffer> data, const KURL& url)
301 {
302     if (!data) {
303         WTF_LOG_ERROR("No data for resource %s", url.string().utf8().data());
304         return;
305     }
306
307     String mimeType = resource->response().mimeType();
308     m_resources->append(SerializedResource(url, mimeType, data));
309     m_resourceURLs.add(url);
310 }
311
312 void PageSerializer::addImageToResources(ImageResource* image, RenderObject* imageRenderer, const KURL& url)
313 {
314     if (!shouldAddURL(url))
315         return;
316
317     if (!image || image->image() == Image::nullImage())
318         return;
319
320     RefPtr<SharedBuffer> data = imageRenderer ? image->imageForRenderer(imageRenderer)->data() : 0;
321     if (!data)
322         data = image->image()->data();
323
324     addToResources(image, data, url);
325 }
326
327 void PageSerializer::addFontToResources(FontResource* font)
328 {
329     if (!font || !shouldAddURL(font->url()) || !font->isLoaded() || !font->resourceBuffer()) {
330         return;
331     }
332     RefPtr<SharedBuffer> data(font->resourceBuffer());
333
334     addToResources(font, data, font->url());
335 }
336
337 void PageSerializer::retrieveResourcesForProperties(const StylePropertySet* styleDeclaration, Document& document)
338 {
339     if (!styleDeclaration)
340         return;
341
342     // The background-image and list-style-image (for ul or ol) are the CSS properties
343     // that make use of images. We iterate to make sure we include any other
344     // image properties there might be.
345     unsigned propertyCount = styleDeclaration->propertyCount();
346     for (unsigned i = 0; i < propertyCount; ++i) {
347         RefPtr<CSSValue> cssValue = styleDeclaration->propertyAt(i).value();
348         retrieveResourcesForCSSValue(cssValue.get(), document);
349     }
350 }
351
352 void PageSerializer::retrieveResourcesForCSSValue(CSSValue* cssValue, Document& document)
353 {
354     if (cssValue->isImageValue()) {
355         CSSImageValue* imageValue = toCSSImageValue(cssValue);
356         StyleImage* styleImage = imageValue->cachedOrPendingImage();
357         // Non cached-images are just place-holders and do not contain data.
358         if (!styleImage || !styleImage->isImageResource())
359             return;
360
361         addImageToResources(styleImage->cachedImage(), 0, styleImage->cachedImage()->url());
362     } else if (cssValue->isFontFaceSrcValue()) {
363         CSSFontFaceSrcValue* fontFaceSrcValue = toCSSFontFaceSrcValue(cssValue);
364         if (fontFaceSrcValue->isLocal()) {
365             return;
366         }
367
368         addFontToResources(fontFaceSrcValue->fetch(&document));
369     } else if (cssValue->isValueList()) {
370         CSSValueList* cssValueList = toCSSValueList(cssValue);
371         for (unsigned i = 0; i < cssValueList->length(); i++)
372             retrieveResourcesForCSSValue(cssValueList->item(i), document);
373     }
374 }
375
376 KURL PageSerializer::urlForBlankFrame(Frame* frame)
377 {
378     HashMap<Frame*, KURL>::iterator iter = m_blankFrameURLs.find(frame);
379     if (iter != m_blankFrameURLs.end())
380         return iter->value;
381     String url = "wyciwyg://frame/" + String::number(m_blankFrameCounter++);
382     KURL fakeURL(ParsedURLString, url);
383     m_blankFrameURLs.add(frame, fakeURL);
384
385     return fakeURL;
386 }
387
388 }