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