6b1d9cb6c5f452acc03f86b3f5ba5c7ae1ed3f14
[WebKit-https.git] / Source / WebCore / 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 "PageSerializer.h"
33
34 #include "CSSImageValue.h"
35 #include "CSSImportRule.h"
36 #include "CSSStyleRule.h"
37 #include "CachedImage.h"
38 #include "Document.h"
39 #include "Element.h"
40 #include "HTMLFrameOwnerElement.h"
41 #include "HTMLHeadElement.h"
42 #include "HTMLImageElement.h"
43 #include "HTMLLinkElement.h"
44 #include "HTMLMetaCharsetParser.h"
45 #include "HTMLNames.h"
46 #include "HTMLStyleElement.h"
47 #include "HTTPParsers.h"
48 #include "Image.h"
49 #include "MIMETypeRegistry.h"
50 #include "MainFrame.h"
51 #include "MarkupAccumulator.h"
52 #include "Page.h"
53 #include "RenderElement.h"
54 #include "StyleCachedImage.h"
55 #include "StyleImage.h"
56 #include "StylePropertySet.h"
57 #include "StyleRule.h"
58 #include "StyleSheetContents.h"
59 #include "Text.h"
60 #include "TextEncoding.h"
61 #include <wtf/text/CString.h>
62 #include <wtf/text/StringBuilder.h>
63 #include <wtf/text/WTFString.h>
64
65 namespace WebCore {
66
67 static bool isCharsetSpecifyingNode(Node* node)
68 {
69     if (!node->isHTMLElement())
70         return false;
71
72     HTMLElement* element = toHTMLElement(node);
73     if (!element->hasTagName(HTMLNames::metaTag))
74         return false;
75     HTMLMetaCharsetParser::AttributeList attributes;
76     if (element->hasAttributes()) {
77         for (unsigned i = 0; i < element->attributeCount(); ++i) {
78             const Attribute& attribute = element->attributeAt(i);
79             // FIXME: We should deal appropriately with the attribute if they have a namespace.
80             attributes.append(std::make_pair(attribute.name().toString(), attribute.value().string()));
81         }
82     }
83     TextEncoding textEncoding = HTMLMetaCharsetParser::encodingFromMetaAttributes(attributes);
84     return textEncoding.isValid();
85 }
86
87 static bool shouldIgnoreElement(Element* element)
88 {
89     return element->hasTagName(HTMLNames::scriptTag) || element->hasTagName(HTMLNames::noscriptTag) || isCharsetSpecifyingNode(element);
90 }
91
92 static const QualifiedName& frameOwnerURLAttributeName(const HTMLFrameOwnerElement& frameOwner)
93 {
94     // FIXME: We should support all frame owners including applets.
95     return isHTMLObjectElement(frameOwner) ? HTMLNames::dataAttr : HTMLNames::srcAttr;
96 }
97
98 class SerializerMarkupAccumulator : public WebCore::MarkupAccumulator {
99 public:
100     SerializerMarkupAccumulator(PageSerializer*, Document*, Vector<Node*>*);
101     virtual ~SerializerMarkupAccumulator();
102
103 protected:
104     virtual void appendText(StringBuilder& out, Text*);
105     virtual void appendElement(StringBuilder& out, Element*, Namespaces*);
106     virtual void appendCustomAttributes(StringBuilder& out, Element*, Namespaces*);
107     virtual void appendEndTag(Node*);
108
109 private:
110     PageSerializer* m_serializer;
111     Document* m_document;
112 };
113
114 SerializerMarkupAccumulator::SerializerMarkupAccumulator(PageSerializer* serializer, Document* document, Vector<Node*>* nodes)
115     : MarkupAccumulator(nodes, ResolveAllURLs)
116     , m_serializer(serializer)
117     , m_document(document)
118 {
119     // MarkupAccumulator does not serialize the <?xml ... line, so we add it explicitely to ensure the right encoding is specified.
120     if (m_document->isXHTMLDocument() || m_document->xmlStandalone() || m_document->isSVGDocument())
121         appendString("<?xml version=\"" + m_document->xmlVersion() + "\" encoding=\"" + m_document->charset() + "\"?>");
122 }
123
124 SerializerMarkupAccumulator::~SerializerMarkupAccumulator()
125 {
126 }
127
128 void SerializerMarkupAccumulator::appendText(StringBuilder& out, Text* text)
129 {
130     Element* parent = text->parentElement();
131     if (parent && !shouldIgnoreElement(parent))
132         MarkupAccumulator::appendText(out, text);
133 }
134
135 void SerializerMarkupAccumulator::appendElement(StringBuilder& out, Element* element, Namespaces* namespaces)
136 {
137     if (!shouldIgnoreElement(element))
138         MarkupAccumulator::appendElement(out, element, namespaces);
139
140     if (element->hasTagName(HTMLNames::headTag)) {
141         out.append("<meta charset=\"");
142         out.append(m_document->charset());
143         out.append("\">");
144     }
145
146     // FIXME: For object (plugins) tags and video tag we could replace them by an image of their current contents.
147 }
148
149 void SerializerMarkupAccumulator::appendCustomAttributes(StringBuilder& out, Element* element, Namespaces* namespaces)
150 {
151     if (!element->isFrameOwnerElement())
152         return;
153
154     HTMLFrameOwnerElement* frameOwner = toFrameOwnerElement(element);
155     Frame* frame = frameOwner->contentFrame();
156     if (!frame)
157         return;
158
159     URL url = frame->document()->url();
160     if (url.isValid() && !url.isBlankURL())
161         return;
162
163     // We need to give a fake location to blank frames so they can be referenced by the serialized frame.
164     url = m_serializer->urlForBlankFrame(frame);
165     appendAttribute(out, element, Attribute(frameOwnerURLAttributeName(*frameOwner), url.string()), namespaces);
166 }
167
168 void SerializerMarkupAccumulator::appendEndTag(Node* node)
169 {
170     if (node->isElementNode() && !shouldIgnoreElement(toElement(node)))
171         MarkupAccumulator::appendEndTag(node);
172 }
173
174 PageSerializer::Resource::Resource()
175 {
176 }
177
178 PageSerializer::Resource::Resource(const URL& url, const String& mimeType, PassRefPtr<SharedBuffer> data)
179     : url(url)
180     , mimeType(mimeType)
181     , data(data)
182 {
183 }
184
185 PageSerializer::PageSerializer(Vector<PageSerializer::Resource>* resources)
186     : m_resources(resources)
187     , m_blankFrameCounter(0)
188 {
189 }
190
191 void PageSerializer::serialize(Page* page)
192 {
193     serializeFrame(&page->mainFrame());
194 }
195
196 void PageSerializer::serializeFrame(Frame* frame)
197 {
198     Document* document = frame->document();
199     URL url = document->url();
200     if (!url.isValid() || url.isBlankURL()) {
201         // For blank frames we generate a fake URL so they can be referenced by their containing frame.
202         url = urlForBlankFrame(frame);
203     }
204
205     if (m_resourceURLs.contains(url)) {
206         // FIXME: We could have 2 frame with the same URL but which were dynamically changed and have now
207         // different content. So we should serialize both and somehow rename the frame src in the containing
208         // frame. Arg!
209         return;
210     }
211
212     Vector<Node*> nodes;
213     SerializerMarkupAccumulator accumulator(this, document, &nodes);
214     TextEncoding textEncoding(document->charset());
215     CString data;
216     if (!textEncoding.isValid()) {
217         // FIXME: iframes used as images trigger this. We should deal with them correctly.
218         return;
219     }
220     String text = accumulator.serializeNodes(document->documentElement(), 0, IncludeNode);
221     CString frameHTML = textEncoding.encode(text.characters(), text.length(), EntitiesForUnencodables);
222     m_resources->append(Resource(url, document->suggestedMIMEType(), SharedBuffer::create(frameHTML.data(), frameHTML.length())));
223     m_resourceURLs.add(url);
224
225     for (Vector<Node*>::iterator iter = nodes.begin(); iter != nodes.end(); ++iter) {
226         Node* node = *iter;
227         if (!node->isElementNode())
228             continue;
229
230         Element* element = toElement(node);
231         // We have to process in-line style as it might contain some resources (typically background images).
232         if (element->isStyledElement())
233             retrieveResourcesForProperties(static_cast<StyledElement*>(element)->inlineStyle(), document);
234
235         if (isHTMLImageElement(element)) {
236             HTMLImageElement* imageElement = toHTMLImageElement(element);
237             URL url = document->completeURL(imageElement->getAttribute(HTMLNames::srcAttr));
238             CachedImage* cachedImage = imageElement->cachedImage();
239             addImageToResources(cachedImage, imageElement->renderer(), url);
240         } else if (element->hasTagName(HTMLNames::linkTag)) {
241             HTMLLinkElement* linkElement = static_cast<HTMLLinkElement*>(element);
242             if (CSSStyleSheet* sheet = linkElement->sheet()) {
243                 URL url = document->completeURL(linkElement->getAttribute(HTMLNames::hrefAttr));
244                 serializeCSSStyleSheet(sheet, url);
245                 ASSERT(m_resourceURLs.contains(url));
246             }
247         } else if (isHTMLStyleElement(element)) {
248             if (CSSStyleSheet* sheet = toHTMLStyleElement(element)->sheet())
249                 serializeCSSStyleSheet(sheet, URL());
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 URL& 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         Document* document = styleSheet->ownerDocument();
269         // Some rules have resources associated with them that we need to retrieve.
270         if (rule->type() == CSSRule::IMPORT_RULE) {
271             CSSImportRule* importRule = static_cast<CSSImportRule*>(rule);
272             URL importURL = document->completeURL(importRule->href());
273             if (m_resourceURLs.contains(importURL))
274                 continue;
275             serializeCSSStyleSheet(importRule->styleSheet(), importURL);
276         } else if (rule->type() == CSSRule::FONT_FACE_RULE) {
277             // FIXME: Add support for font face rule. It is not clear to me at this point if the actual otf/eot file can
278             // be retrieved from the CSSFontFaceRule object.
279         } else if (rule->type() == CSSRule::STYLE_RULE)
280             retrieveResourcesForRule(static_cast<CSSStyleRule*>(rule)->styleRule(), document);
281     }
282
283     if (url.isValid() && !m_resourceURLs.contains(url)) {
284         // FIXME: We should check whether a charset has been specified and if none was found add one.
285         TextEncoding textEncoding(styleSheet->contents()->charset());
286         ASSERT(textEncoding.isValid());
287         String textString = cssText.toString();
288         CString text = textEncoding.encode(textString.characters(), textString.length(), EntitiesForUnencodables);
289         m_resources->append(Resource(url, String("text/css"), SharedBuffer::create(text.data(), text.length())));
290         m_resourceURLs.add(url);
291     }
292 }
293
294 void PageSerializer::addImageToResources(CachedImage* image, RenderElement* imageRenderer, const URL& url)
295 {
296     if (!url.isValid() || m_resourceURLs.contains(url))
297         return;
298
299     if (!image || image->image() == Image::nullImage())
300         return;
301
302     RefPtr<SharedBuffer> data = imageRenderer ? image->imageForRenderer(imageRenderer)->data() : 0;
303     if (!data)
304         data = image->image()->data();
305
306     if (!data) {
307         LOG_ERROR("No data for image %s", url.string().utf8().data());
308         return;
309     }
310
311     String mimeType = image->response().mimeType();
312     m_resources->append(Resource(url, mimeType, data));
313     m_resourceURLs.add(url);
314 }
315
316 void PageSerializer::retrieveResourcesForRule(StyleRule* rule, Document* document)
317 {
318     retrieveResourcesForProperties(&rule->properties(), document);
319 }
320
321 void PageSerializer::retrieveResourcesForProperties(const StylePropertySet* styleDeclaration, Document* document)
322 {
323     if (!styleDeclaration)
324         return;
325
326     // The background-image and list-style-image (for ul or ol) are the CSS properties
327     // that make use of images. We iterate to make sure we include any other
328     // image properties there might be.
329     unsigned propertyCount = styleDeclaration->propertyCount();
330     for (unsigned i = 0; i < propertyCount; ++i) {
331         RefPtr<CSSValue> cssValue = styleDeclaration->propertyAt(i).value();
332         if (!cssValue->isImageValue())
333             continue;
334
335         StyleImage* styleImage = toCSSImageValue(cssValue.get())->cachedOrPendingImage();
336         // Non cached-images are just place-holders and do not contain data.
337         if (!styleImage || !styleImage->isCachedImage())
338             continue;
339
340         CachedImage* image = static_cast<StyleCachedImage*>(styleImage)->cachedImage();
341
342         URL url = document->completeURL(image->url());
343         addImageToResources(image, 0, url);
344     }
345 }
346
347 URL PageSerializer::urlForBlankFrame(Frame* frame)
348 {
349     HashMap<Frame*, URL>::iterator iter = m_blankFrameURLs.find(frame);
350     if (iter != m_blankFrameURLs.end())
351         return iter->value;
352     String url = "wyciwyg://frame/" + String::number(m_blankFrameCounter++);
353     URL fakeURL(ParsedURLString, url);
354     m_blankFrameURLs.add(frame, fakeURL);
355
356     return fakeURL;
357 }
358
359 }