Unprefix CSS cursor values zoom-in and zoom-out.
[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 "DocumentLoader.h"
31 #include "EventListener.h"
32 #include "EventNames.h"
33 #include "ExceptionCodePlaceholder.h"
34 #include "FrameLoader.h"
35 #include "FrameLoaderClient.h"
36 #include "FrameView.h"
37 #include "HTMLBodyElement.h"
38 #include "HTMLHtmlElement.h"
39 #include "HTMLImageElement.h"
40 #include "HTMLNames.h"
41 #include "LocalizedStrings.h"
42 #include "MIMETypeRegistry.h"
43 #include "MainFrame.h"
44 #include "MouseEvent.h"
45 #include "Page.h"
46 #include "RawDataDocumentParser.h"
47 #include "RenderElement.h"
48 #include "ResourceBuffer.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 PassRefPtr<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 PassRefPtr<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 PassRefPtr<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 PassRefPtr<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     m_imageElement->cachedImage()->addDataBuffer(loader()->mainResourceData().get());
138
139     imageUpdated();
140 }
141
142 void ImageDocument::finishedParsing()
143 {
144     if (!parser()->isStopped() && m_imageElement) {
145         CachedImage& cachedImage = *m_imageElement->cachedImage();
146         RefPtr<ResourceBuffer> data = loader()->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 (loader()->isLoadingMultipartContent())
151             data = data->copy();
152
153         cachedImage.finishLoading(data.get());
154         cachedImage.finish();
155
156         // Report the natural image size in the page title, regardless of zoom level.
157         // At a zoom level of 1 the image is guaranteed to have an integer size.
158         updateStyleIfNeeded();
159         IntSize size = flooredIntSize(cachedImage.imageSizeForRenderer(m_imageElement->renderer(), 1));
160         if (size.width()) {
161             // Compute the title. We use the decoded filename of the resource, falling
162             // back on the hostname if there is no path.
163             String name = decodeURLEscapeSequences(url().lastPathComponent());
164             if (name.isEmpty())
165                 name = url().host();
166             setTitle(imageTitle(name, size));
167         }
168
169         imageUpdated();
170     }
171
172     HTMLDocument::finishedParsing();
173 }
174     
175 inline ImageDocument& ImageDocumentParser::document() const
176 {
177     // Only used during parsing, so document is guaranteed to be non-null.
178     ASSERT(RawDataDocumentParser::document());
179     return toImageDocument(*RawDataDocumentParser::document());
180 }
181
182 void ImageDocumentParser::appendBytes(DocumentWriter&, const char*, size_t)
183 {
184     document().updateDuringParsing();
185 }
186
187 void ImageDocumentParser::finish()
188 {
189     document().finishedParsing();
190 }
191
192 ImageDocument::ImageDocument(Frame& frame, const URL& url)
193     : HTMLDocument(&frame, url, ImageDocumentClass)
194     , m_imageElement(nullptr)
195     , m_imageSizeIsKnown(false)
196 #if !PLATFORM(IOS)
197     , m_didShrinkImage(false)
198 #endif
199     , m_shouldShrinkImage(frame.settings().shrinksStandaloneImagesToFit() && frame.isMainFrame())
200 {
201     setCompatibilityMode(DocumentCompatibilityMode::QuirksMode);
202     lockCompatibilityMode();
203 }
204     
205 PassRefPtr<DocumentParser> ImageDocument::createParser()
206 {
207     return ImageDocumentParser::create(*this);
208 }
209
210 void ImageDocument::createDocumentStructure()
211 {
212     RefPtr<Element> rootElement = Document::createElement(htmlTag, false);
213     appendChild(rootElement);
214     downcast<HTMLHtmlElement>(*rootElement).insertedByParser();
215
216     frame()->injectUserScripts(InjectAtDocumentStart);
217
218     RefPtr<Element> body = Document::createElement(bodyTag, false);
219     body->setAttribute(styleAttr, "margin: 0px");
220     if (MIMETypeRegistry::isPDFMIMEType(document().loader()->responseMIMEType()))
221         downcast<HTMLBodyElement>(*body).setInlineStyleProperty(CSSPropertyBackgroundColor, "white", CSSPrimitiveValue::CSS_IDENT);
222     rootElement->appendChild(body);
223     
224     RefPtr<ImageDocumentElement> imageElement = ImageDocumentElement::create(*this);
225     if (m_shouldShrinkImage)
226         imageElement->setAttribute(styleAttr, "-webkit-user-select:none; display:block; margin:auto;");
227     else
228         imageElement->setAttribute(styleAttr, "-webkit-user-select:none;");
229     imageElement->setLoadManually(true);
230     imageElement->setSrc(url().string());
231     imageElement->cachedImage()->setResponse(loader()->response());
232     body->appendChild(imageElement);
233     
234     if (m_shouldShrinkImage) {
235 #if PLATFORM(IOS)
236         // Set the viewport to be in device pixels (rather than the default of 980).
237         processViewport(ASCIILiteral("width=device-width"), ViewportArguments::ImageDocument);
238 #else
239         RefPtr<EventListener> listener = ImageEventListener::create(*this);
240         if (DOMWindow* window = this->domWindow())
241             window->addEventListener("resize", listener, false);
242         imageElement->addEventListener("click", listener.release(), false);
243 #endif
244     }
245
246     m_imageElement = imageElement.get();
247 }
248
249 void ImageDocument::imageUpdated()
250 {
251     ASSERT(m_imageElement);
252
253     if (m_imageSizeIsKnown)
254         return;
255
256     LayoutSize imageSize = this->imageSize();
257     if (imageSize.isEmpty())
258         return;
259
260     m_imageSizeIsKnown = true;
261
262     if (m_shouldShrinkImage) {
263 #if PLATFORM(IOS)
264         FloatSize screenSize = page()->chrome().screenSize();
265         if (imageSize.width() > screenSize.width())
266             processViewport(String::format("width=%u", static_cast<unsigned>(imageSize.width().toInt())), ViewportArguments::ImageDocument);
267 #else
268         // Call windowSizeChanged for its side effect of sizing the image.
269         windowSizeChanged();
270 #endif
271     }
272 }
273
274 #if !PLATFORM(IOS)
275 float ImageDocument::scale()
276 {
277     if (!m_imageElement)
278         return 1;
279
280     FrameView* view = this->view();
281     if (!view)
282         return 1;
283
284     LayoutSize imageSize = this->imageSize();
285
286     IntSize viewportSize = view->visibleSize();
287     float widthScale = viewportSize.width() / imageSize.width().toFloat();
288     float heightScale = viewportSize.height() / imageSize.height().toFloat();
289
290     return std::min(widthScale, heightScale);
291 }
292
293 void ImageDocument::resizeImageToFit()
294 {
295     if (!m_imageElement)
296         return;
297
298     LayoutSize imageSize = this->imageSize();
299
300     float scale = this->scale();
301     m_imageElement->setWidth(static_cast<int>(imageSize.width() * scale));
302     m_imageElement->setHeight(static_cast<int>(imageSize.height() * scale));
303
304     m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueZoomIn);
305 }
306
307 void ImageDocument::restoreImageSize()
308 {
309     if (!m_imageElement || !m_imageSizeIsKnown)
310         return;
311
312     LayoutSize imageSize = this->imageSize();
313     m_imageElement->setWidth(imageSize.width());
314     m_imageElement->setHeight(imageSize.height());
315
316     if (imageFitsInWindow())
317         m_imageElement->removeInlineStyleProperty(CSSPropertyCursor);
318     else
319         m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueZoomOut);
320
321     m_didShrinkImage = false;
322 }
323
324 bool ImageDocument::imageFitsInWindow()
325 {
326     if (!m_imageElement)
327         return true;
328
329     FrameView* view = this->view();
330     if (!view)
331         return true;
332
333     LayoutSize imageSize = this->imageSize();
334     IntSize viewportSize = view->visibleSize();
335     return imageSize.width() <= viewportSize.width() && imageSize.height() <= viewportSize.height();
336 }
337
338
339 void ImageDocument::windowSizeChanged()
340 {
341     if (!m_imageElement || !m_imageSizeIsKnown)
342         return;
343
344     bool fitsInWindow = imageFitsInWindow();
345
346     // If the image has been explicitly zoomed in, restore the cursor if the image fits
347     // and set it to a zoom out cursor if the image doesn't fit
348     if (!m_shouldShrinkImage) {
349         if (fitsInWindow)
350             m_imageElement->removeInlineStyleProperty(CSSPropertyCursor);
351         else
352             m_imageElement->setInlineStyleProperty(CSSPropertyCursor, CSSValueZoomOut);
353         return;
354     }
355
356     if (m_didShrinkImage) {
357         // If the window has been resized so that the image fits, restore the image size,
358         // otherwise update the restored image size.
359         if (fitsInWindow)
360             restoreImageSize();
361         else
362             resizeImageToFit();
363     } else {
364         // If the image isn't resized but needs to be, then resize it.
365         if (!fitsInWindow) {
366             resizeImageToFit();
367             m_didShrinkImage = true;
368         }
369     }
370 }
371
372 void ImageDocument::imageClicked(int x, int y)
373 {
374     if (!m_imageSizeIsKnown || imageFitsInWindow())
375         return;
376
377     m_shouldShrinkImage = !m_shouldShrinkImage;
378
379     if (m_shouldShrinkImage) {
380         // Call windowSizeChanged for its side effect of sizing the image.
381         windowSizeChanged();
382     } else {
383         restoreImageSize();
384
385         updateLayout();
386
387         float scale = this->scale();
388
389         IntSize viewportSize = view()->visibleSize();
390         int scrollX = static_cast<int>(x / scale - viewportSize.width() / 2.0f);
391         int scrollY = static_cast<int>(y / scale - viewportSize.height() / 2.0f);
392
393         view()->setScrollPosition(IntPoint(scrollX, scrollY));
394     }
395 }
396
397 void ImageEventListener::handleEvent(ScriptExecutionContext*, Event* event)
398 {
399     if (event->type() == eventNames().resizeEvent)
400         m_document.windowSizeChanged();
401     else if (event->type() == eventNames().clickEvent && event->isMouseEvent()) {
402         MouseEvent& mouseEvent = toMouseEvent(*event);
403         m_document.imageClicked(mouseEvent.x(), mouseEvent.y());
404     }
405 }
406
407 bool ImageEventListener::operator==(const EventListener& other)
408 {
409     // All ImageEventListener objects compare as equal; OK since there is only one per document.
410     return other.type() == ImageEventListenerType;
411 }
412 #endif
413
414 // --------
415
416 ImageDocumentElement::~ImageDocumentElement()
417 {
418     if (m_imageDocument)
419         m_imageDocument->disconnectImageElement();
420 }
421
422 void ImageDocumentElement::didMoveToNewDocument(Document* oldDocument)
423 {
424     if (m_imageDocument) {
425         m_imageDocument->disconnectImageElement();
426         m_imageDocument = nullptr;
427     }
428     HTMLImageElement::didMoveToNewDocument(oldDocument);
429 }
430
431 }