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