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