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