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