Node.appendChild(null) / replaceChild(null, null) / removeChild(null) / insertBefore...
[WebKit-https.git] / Source / WebCore / html / ImageDocument.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2010, 2014 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
20  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
23  */
24
25 #include "config.h"
26 #include "ImageDocument.h"
27
28 #include "CachedImage.h"
29 #include "Chrome.h"
30 #include "ChromeClient.h"
31 #include "DocumentLoader.h"
32 #include "EventListener.h"
33 #include "EventNames.h"
34 #include "ExceptionCodePlaceholder.h"
35 #include "FrameLoader.h"
36 #include "FrameLoaderClient.h"
37 #include "FrameView.h"
38 #include "HTMLBodyElement.h"
39 #include "HTMLHtmlElement.h"
40 #include "HTMLImageElement.h"
41 #include "HTMLNames.h"
42 #include "LocalizedStrings.h"
43 #include "MIMETypeRegistry.h"
44 #include "MainFrame.h"
45 #include "MouseEvent.h"
46 #include "Page.h"
47 #include "RawDataDocumentParser.h"
48 #include "RenderElement.h"
49 #include "Settings.h"
50
51 namespace WebCore {
52
53 using namespace HTMLNames;
54
55 #if !PLATFORM(IOS)
56 class ImageEventListener final : public EventListener {
57 public:
58     static Ref<ImageEventListener> create(ImageDocument& document) { return adoptRef(*new ImageEventListener(document)); }
59
60 private:
61     ImageEventListener(ImageDocument& document)
62         : EventListener(ImageEventListenerType)
63         , m_document(document)
64     {
65     }
66
67     virtual bool operator==(const EventListener&) override;
68     virtual void handleEvent(ScriptExecutionContext*, Event*) override;
69
70     ImageDocument& m_document;
71 };
72 #endif
73
74 class ImageDocumentParser final : public RawDataDocumentParser {
75 public:
76     static Ref<ImageDocumentParser> create(ImageDocument& document)
77     {
78         return adoptRef(*new ImageDocumentParser(document));
79     }
80
81 private:
82     ImageDocumentParser(ImageDocument& document)
83         : RawDataDocumentParser(document)
84     {
85     }
86
87     ImageDocument& document() const;
88
89     virtual void appendBytes(DocumentWriter&, const char*, size_t) override;
90     virtual void finish() override;
91 };
92
93 class ImageDocumentElement final : public HTMLImageElement {
94 public:
95     static Ref<ImageDocumentElement> create(ImageDocument&);
96
97 private:
98     ImageDocumentElement(ImageDocument& document)
99         : HTMLImageElement(imgTag, document)
100         , m_imageDocument(&document)
101     {
102     }
103
104     virtual ~ImageDocumentElement();
105     virtual void didMoveToNewDocument(Document* oldDocument) override;
106
107     ImageDocument* m_imageDocument;
108 };
109
110 inline Ref<ImageDocumentElement> ImageDocumentElement::create(ImageDocument& document)
111 {
112     return adoptRef(*new ImageDocumentElement(document));
113 }
114
115 // --------
116
117 HTMLImageElement* ImageDocument::imageElement() const
118 {
119     return m_imageElement;
120 }
121
122 LayoutSize ImageDocument::imageSize()
123 {
124     ASSERT(m_imageElement);
125     updateStyleIfNeeded();
126     return m_imageElement->cachedImage()->imageSizeForRenderer(m_imageElement->renderer(), frame() ? frame()->pageZoomFactor() : 1);
127 }
128
129 void ImageDocument::updateDuringParsing()
130 {
131     if (!frame()->settings().areImagesEnabled())
132         return;
133
134     if (!m_imageElement)
135         createDocumentStructure();
136
137     if (RefPtr<SharedBuffer> buffer = loader()->mainResourceData())
138         m_imageElement->cachedImage()->addDataBuffer(*buffer);
139
140     imageUpdated();
141 }
142
143 void ImageDocument::finishedParsing()
144 {
145     if (!parser()->isStopped() && m_imageElement) {
146         CachedImage& cachedImage = *m_imageElement->cachedImage();
147         RefPtr<SharedBuffer> data = loader()->mainResourceData();
148
149         // If this is a multipart image, make a copy of the current part, since the resource data
150         // will be overwritten by the next part.
151         if (data && loader()->isLoadingMultipartContent())
152             data = data->copy();
153
154         cachedImage.finishLoading(data.get());
155         cachedImage.finish();
156
157         // Report the natural image size in the page title, regardless of zoom level.
158         // At a zoom level of 1 the image is guaranteed to have an integer size.
159         updateStyleIfNeeded();
160         IntSize size = flooredIntSize(cachedImage.imageSizeForRenderer(m_imageElement->renderer(), 1));
161         if (size.width()) {
162             // Compute the title. We use the decoded filename of the resource, falling
163             // back on the hostname if there is no path.
164             String name = decodeURLEscapeSequences(url().lastPathComponent());
165             if (name.isEmpty())
166                 name = url().host();
167             setTitle(imageTitle(name, size));
168         }
169
170         imageUpdated();
171     }
172
173     HTMLDocument::finishedParsing();
174 }
175     
176 inline ImageDocument& ImageDocumentParser::document() const
177 {
178     // Only used during parsing, so document is guaranteed to be non-null.
179     ASSERT(RawDataDocumentParser::document());
180     return downcast<ImageDocument>(*RawDataDocumentParser::document());
181 }
182
183 void ImageDocumentParser::appendBytes(DocumentWriter&, const char*, size_t)
184 {
185     document().updateDuringParsing();
186 }
187
188 void ImageDocumentParser::finish()
189 {
190     document().finishedParsing();
191 }
192
193 ImageDocument::ImageDocument(Frame& frame, const URL& url)
194     : HTMLDocument(&frame, url, ImageDocumentClass)
195     , m_imageElement(nullptr)
196     , m_imageSizeIsKnown(false)
197 #if !PLATFORM(IOS)
198     , m_didShrinkImage(false)
199 #endif
200     , m_shouldShrinkImage(frame.settings().shrinksStandaloneImagesToFit() && frame.isMainFrame())
201 {
202     setCompatibilityMode(DocumentCompatibilityMode::QuirksMode);
203     lockCompatibilityMode();
204 }
205     
206 Ref<DocumentParser> ImageDocument::createParser()
207 {
208     return ImageDocumentParser::create(*this);
209 }
210
211 void ImageDocument::createDocumentStructure()
212 {
213     Ref<Element> rootElement = Document::createElement(htmlTag, false);
214     appendChild(rootElement.copyRef());
215     downcast<HTMLHtmlElement>(rootElement.get()).insertedByParser();
216
217     frame()->injectUserScripts(InjectAtDocumentStart);
218
219     Ref<Element> body = Document::createElement(bodyTag, false);
220     body->setAttribute(styleAttr, "margin: 0px");
221     if (MIMETypeRegistry::isPDFMIMEType(document().loader()->responseMIMEType()))
222         downcast<HTMLBodyElement>(body.get()).setInlineStyleProperty(CSSPropertyBackgroundColor, "white", CSSPrimitiveValue::CSS_IDENT);
223     rootElement->appendChild(body.copyRef());
224     
225     Ref<ImageDocumentElement> imageElement = ImageDocumentElement::create(*this);
226     if (m_shouldShrinkImage)
227         imageElement->setAttribute(styleAttr, "-webkit-user-select:none; display:block; margin:auto;");
228     else
229         imageElement->setAttribute(styleAttr, "-webkit-user-select:none;");
230     imageElement->setLoadManually(true);
231     imageElement->setSrc(url().string());
232     imageElement->cachedImage()->setResponse(loader()->response());
233     body->appendChild(imageElement.copyRef());
234     
235     if (m_shouldShrinkImage) {
236 #if PLATFORM(IOS)
237         // Set the viewport to be in device pixels (rather than the default of 980).
238         processViewport(ASCIILiteral("width=device-width"), ViewportArguments::ImageDocument);
239 #else
240         RefPtr<EventListener> listener = ImageEventListener::create(*this);
241         if (DOMWindow* window = this->domWindow())
242             window->addEventListener("resize", listener.copyRef(), false);
243         imageElement->addEventListener("click", WTF::move(listener), false);
244 #endif
245     }
246
247     m_imageElement = imageElement.ptr();
248 }
249
250 void ImageDocument::imageUpdated()
251 {
252     ASSERT(m_imageElement);
253
254     if (m_imageSizeIsKnown)
255         return;
256
257     LayoutSize imageSize = this->imageSize();
258     if (imageSize.isEmpty())
259         return;
260
261     m_imageSizeIsKnown = true;
262
263     if (m_shouldShrinkImage) {
264 #if PLATFORM(IOS)
265         FloatSize screenSize = page()->chrome().screenSize();
266         if (imageSize.width() > screenSize.width())
267             processViewport(String::format("width=%u", static_cast<unsigned>(imageSize.width().toInt())), ViewportArguments::ImageDocument);
268         if (page())
269             page()->chrome().client().imageOrMediaDocumentSizeChanged(IntSize(imageSize.width(), imageSize.height()));
270 #else
271         // Call windowSizeChanged for its side effect of sizing the image.
272         windowSizeChanged();
273 #endif
274     }
275 }
276
277 #if !PLATFORM(IOS)
278 float ImageDocument::scale()
279 {
280     if (!m_imageElement)
281         return 1;
282
283     FrameView* view = this->view();
284     if (!view)
285         return 1;
286
287     LayoutSize imageSize = this->imageSize();
288
289     IntSize viewportSize = view->visibleSize();
290     float widthScale = viewportSize.width() / imageSize.width().toFloat();
291     float heightScale = viewportSize.height() / imageSize.height().toFloat();
292
293     return std::min(widthScale, heightScale);
294 }
295
296 void ImageDocument::resizeImageToFit()
297 {
298     if (!m_imageElement)
299         return;
300
301     LayoutSize imageSize = this->imageSize();
302
303     float scale = this->scale();
304     m_imageElement->setWidth(static_cast<int>(imageSize.width() * scale));
305     m_imageElement->setHeight(static_cast<int>(imageSize.height() * scale));
306
307     m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueZoomIn);
308 }
309
310 void ImageDocument::restoreImageSize()
311 {
312     if (!m_imageElement || !m_imageSizeIsKnown)
313         return;
314
315     LayoutSize imageSize = this->imageSize();
316     m_imageElement->setWidth(imageSize.width());
317     m_imageElement->setHeight(imageSize.height());
318
319     if (imageFitsInWindow())
320         m_imageElement->removeInlineStyleProperty(CSSPropertyCursor);
321     else
322         m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueZoomOut);
323
324     m_didShrinkImage = false;
325 }
326
327 bool ImageDocument::imageFitsInWindow()
328 {
329     if (!m_imageElement)
330         return true;
331
332     FrameView* view = this->view();
333     if (!view)
334         return true;
335
336     LayoutSize imageSize = this->imageSize();
337     IntSize viewportSize = view->visibleSize();
338     return imageSize.width() <= viewportSize.width() && imageSize.height() <= viewportSize.height();
339 }
340
341
342 void ImageDocument::windowSizeChanged()
343 {
344     if (!m_imageElement || !m_imageSizeIsKnown)
345         return;
346
347     bool fitsInWindow = imageFitsInWindow();
348
349     // If the image has been explicitly zoomed in, restore the cursor if the image fits
350     // and set it to a zoom out cursor if the image doesn't fit
351     if (!m_shouldShrinkImage) {
352         if (fitsInWindow)
353             m_imageElement->removeInlineStyleProperty(CSSPropertyCursor);
354         else
355             m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueZoomOut);
356         return;
357     }
358
359     if (m_didShrinkImage) {
360         // If the window has been resized so that the image fits, restore the image size,
361         // otherwise update the restored image size.
362         if (fitsInWindow)
363             restoreImageSize();
364         else
365             resizeImageToFit();
366     } else {
367         // If the image isn't resized but needs to be, then resize it.
368         if (!fitsInWindow) {
369             resizeImageToFit();
370             m_didShrinkImage = true;
371         }
372     }
373 }
374
375 void ImageDocument::imageClicked(int x, int y)
376 {
377     if (!m_imageSizeIsKnown || imageFitsInWindow())
378         return;
379
380     m_shouldShrinkImage = !m_shouldShrinkImage;
381
382     if (m_shouldShrinkImage) {
383         // Call windowSizeChanged for its side effect of sizing the image.
384         windowSizeChanged();
385     } else {
386         restoreImageSize();
387
388         updateLayout();
389
390         float scale = this->scale();
391
392         IntSize viewportSize = view()->visibleSize();
393         int scrollX = static_cast<int>(x / scale - viewportSize.width() / 2.0f);
394         int scrollY = static_cast<int>(y / scale - viewportSize.height() / 2.0f);
395
396         view()->setScrollPosition(IntPoint(scrollX, scrollY));
397     }
398 }
399
400 void ImageEventListener::handleEvent(ScriptExecutionContext*, Event* event)
401 {
402     if (event->type() == eventNames().resizeEvent)
403         m_document.windowSizeChanged();
404     else if (event->type() == eventNames().clickEvent && is<MouseEvent>(*event)) {
405         MouseEvent& mouseEvent = downcast<MouseEvent>(*event);
406         m_document.imageClicked(mouseEvent.offsetX(), mouseEvent.offsetY());
407     }
408 }
409
410 bool ImageEventListener::operator==(const EventListener& other)
411 {
412     // All ImageEventListener objects compare as equal; OK since there is only one per document.
413     return other.type() == ImageEventListenerType;
414 }
415 #endif
416
417 // --------
418
419 ImageDocumentElement::~ImageDocumentElement()
420 {
421     if (m_imageDocument)
422         m_imageDocument->disconnectImageElement();
423 }
424
425 void ImageDocumentElement::didMoveToNewDocument(Document* oldDocument)
426 {
427     if (m_imageDocument) {
428         m_imageDocument->disconnectImageElement();
429         m_imageDocument = nullptr;
430     }
431     HTMLImageElement::didMoveToNewDocument(oldDocument);
432 }
433
434 }