1 /*
2 * Copyright (C) 2006, 2007, 2008 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 "CSSStyleDeclaration.h"
29 #include "CachedImage.h"
30 #include "DocumentLoader.h"
31 #include "Element.h"
32 #include "EventListener.h"
33 #include "EventNames.h"
34 #include "Frame.h"
35 #include "FrameLoader.h"
36 #include "FrameView.h"
37 #include "HTMLImageElement.h"
38 #include "HTMLNames.h"
39 #include "LocalizedStrings.h"
40 #include "MouseEvent.h"
41 #include "NotImplemented.h"
42 #include "Page.h"
43 #include "SegmentedString.h"
44 #include "Settings.h"
45 #include "Text.h"
46 #include "XMLTokenizer.h"
47
48 using std::min;
49
50 namespace WebCore {
51
52 using namespace HTMLNames;
53
54 class ImageEventListener : public EventListener {
55 public:
create(ImageDocument * document)56 static PassRefPtr<ImageEventListener> create(ImageDocument* document) { return adoptRef(new ImageEventListener(document)); }
57 virtual void handleEvent(Event*, bool isWindowEvent);
58
59 private:
ImageEventListener(ImageDocument * document)60 ImageEventListener(ImageDocument* document) : m_doc(document) { }
61 ImageDocument* m_doc;
62 };
63
64 class ImageTokenizer : public Tokenizer {
65 public:
ImageTokenizer(ImageDocument * doc)66 ImageTokenizer(ImageDocument* doc) : m_doc(doc) {}
67
68 virtual void write(const SegmentedString&, bool appendData);
69 virtual void finish();
70 virtual bool isWaitingForScripts() const;
71
wantsRawData() const72 virtual bool wantsRawData() const { return true; }
73 virtual bool writeRawData(const char* data, int len);
74
75 private:
76 ImageDocument* m_doc;
77 };
78
79 class ImageDocumentElement : public HTMLImageElement {
80 public:
ImageDocumentElement(ImageDocument * doc)81 ImageDocumentElement(ImageDocument* doc)
82 : HTMLImageElement(imgTag, doc)
83 , m_imageDocument(doc)
84 {
85 }
86
87 virtual ~ImageDocumentElement();
88 virtual void willMoveToNewOwnerDocument();
89
90 private:
91 ImageDocument* m_imageDocument;
92 };
93
94 // --------
95
write(const SegmentedString &,bool)96 void ImageTokenizer::write(const SegmentedString&, bool)
97 {
98 // <https://bugs.webkit.org/show_bug.cgi?id=25397>: JS code can always call document.write, we need to handle it.
99 notImplemented();
100 }
101
writeRawData(const char *,int)102 bool ImageTokenizer::writeRawData(const char*, int)
103 {
104 CachedImage* cachedImage = m_doc->cachedImage();
105 cachedImage->data(m_doc->frame()->loader()->documentLoader()->mainResourceData(), false);
106
107 m_doc->imageChanged();
108
109 return false;
110 }
111
finish()112 void ImageTokenizer::finish()
113 {
114 if (!m_parserStopped && m_doc->imageElement()) {
115 CachedImage* cachedImage = m_doc->cachedImage();
116 RefPtr<SharedBuffer> data = m_doc->frame()->loader()->documentLoader()->mainResourceData();
117
118 // If this is a multipart image, make a copy of the current part, since the resource data
119 // will be overwritten by the next part.
120 if (m_doc->frame()->loader()->documentLoader()->isLoadingMultipartContent())
121 data = data->copy();
122
123 cachedImage->data(data.release(), true);
124 cachedImage->finish();
125
126 cachedImage->setResponse(m_doc->frame()->loader()->documentLoader()->response());
127
128 IntSize size = cachedImage->imageSize(m_doc->frame()->pageZoomFactor());
129 if (size.width()) {
130 // Compute the title, we use the decoded filename of the resource, falling
131 // back on the (decoded) hostname if there is no path.
132 String fileName = decodeURLEscapeSequences(m_doc->url().lastPathComponent());
133 if (fileName.isEmpty())
134 fileName = m_doc->url().host();
135 m_doc->setTitle(imageTitle(fileName, size));
136 }
137
138 m_doc->imageChanged();
139 }
140
141 m_doc->finishedParsing();
142 }
143
isWaitingForScripts() const144 bool ImageTokenizer::isWaitingForScripts() const
145 {
146 // An image document is never waiting for scripts
147 return false;
148 }
149
150 // --------
151
ImageDocument(Frame * frame)152 ImageDocument::ImageDocument(Frame* frame)
153 : HTMLDocument(frame)
154 , m_imageElement(0)
155 , m_imageSizeIsKnown(false)
156 , m_didShrinkImage(false)
157 , m_shouldShrinkImage(shouldShrinkToFit())
158 {
159 setParseMode(Compat);
160 }
161
createTokenizer()162 Tokenizer* ImageDocument::createTokenizer()
163 {
164 return new ImageTokenizer(this);
165 }
166
createDocumentStructure()167 void ImageDocument::createDocumentStructure()
168 {
169 ExceptionCode ec;
170
171 RefPtr<Element> rootElement = Document::createElement(htmlTag, false);
172 appendChild(rootElement, ec);
173
174 RefPtr<Element> body = Document::createElement(bodyTag, false);
175 body->setAttribute(styleAttr, "margin: 0px;");
176
177 rootElement->appendChild(body, ec);
178
179 RefPtr<ImageDocumentElement> imageElement = new ImageDocumentElement(this);
180
181 imageElement->setAttribute(styleAttr, "-webkit-user-select: none");
182 imageElement->setLoadManually(true);
183 imageElement->setSrc(url().string());
184
185 body->appendChild(imageElement, ec);
186
187 if (shouldShrinkToFit()) {
188 // Add event listeners
189 RefPtr<EventListener> listener = ImageEventListener::create(this);
190 if (DOMWindow* domWindow = this->domWindow())
191 domWindow->addEventListener("resize", listener, false);
192 imageElement->addEventListener("click", listener.release(), false);
193 }
194
195 m_imageElement = imageElement.get();
196 }
197
scale() const198 float ImageDocument::scale() const
199 {
200 if (!m_imageElement)
201 return 1.0f;
202
203 IntSize imageSize = m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor());
204 IntSize windowSize = IntSize(frame()->view()->width(), frame()->view()->height());
205
206 float widthScale = (float)windowSize.width() / imageSize.width();
207 float heightScale = (float)windowSize.height() / imageSize.height();
208
209 return min(widthScale, heightScale);
210 }
211
resizeImageToFit()212 void ImageDocument::resizeImageToFit()
213 {
214 if (!m_imageElement)
215 return;
216
217 IntSize imageSize = m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor());
218
219 float scale = this->scale();
220 m_imageElement->setWidth(static_cast<int>(imageSize.width() * scale));
221 m_imageElement->setHeight(static_cast<int>(imageSize.height() * scale));
222
223 ExceptionCode ec;
224 m_imageElement->style()->setProperty("cursor", "-webkit-zoom-in", ec);
225 }
226
imageClicked(int x,int y)227 void ImageDocument::imageClicked(int x, int y)
228 {
229 if (!m_imageSizeIsKnown || imageFitsInWindow())
230 return;
231
232 m_shouldShrinkImage = !m_shouldShrinkImage;
233
234 if (m_shouldShrinkImage)
235 windowSizeChanged();
236 else {
237 restoreImageSize();
238
239 updateLayout();
240
241 float scale = this->scale();
242
243 int scrollX = static_cast<int>(x / scale - (float)frame()->view()->width() / 2);
244 int scrollY = static_cast<int>(y / scale - (float)frame()->view()->height() / 2);
245
246 frame()->view()->setScrollPosition(IntPoint(scrollX, scrollY));
247 }
248 }
249
imageChanged()250 void ImageDocument::imageChanged()
251 {
252 ASSERT(m_imageElement);
253
254 if (m_imageSizeIsKnown)
255 return;
256
257 if (m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()).isEmpty())
258 return;
259
260 m_imageSizeIsKnown = true;
261
262 if (shouldShrinkToFit()) {
263 // Force resizing of the image
264 windowSizeChanged();
265 }
266 }
267
restoreImageSize()268 void ImageDocument::restoreImageSize()
269 {
270 if (!m_imageElement || !m_imageSizeIsKnown)
271 return;
272
273 m_imageElement->setWidth(m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()).width());
274 m_imageElement->setHeight(m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()).height());
275
276 ExceptionCode ec;
277 if (imageFitsInWindow())
278 m_imageElement->style()->removeProperty("cursor", ec);
279 else
280 m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec);
281
282 m_didShrinkImage = false;
283 }
284
imageFitsInWindow() const285 bool ImageDocument::imageFitsInWindow() const
286 {
287 if (!m_imageElement)
288 return true;
289
290 IntSize imageSize = m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor());
291 IntSize windowSize = IntSize(frame()->view()->width(), frame()->view()->height());
292
293 return imageSize.width() <= windowSize.width() && imageSize.height() <= windowSize.height();
294 }
295
windowSizeChanged()296 void ImageDocument::windowSizeChanged()
297 {
298 if (!m_imageElement || !m_imageSizeIsKnown)
299 return;
300
301 bool fitsInWindow = imageFitsInWindow();
302
303 // If the image has been explicitly zoomed in, restore the cursor if the image fits
304 // and set it to a zoom out cursor if the image doesn't fit
305 if (!m_shouldShrinkImage) {
306 ExceptionCode ec;
307
308 if (fitsInWindow)
309 m_imageElement->style()->removeProperty("cursor", ec);
310 else
311 m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec);
312 return;
313 }
314
315 if (m_didShrinkImage) {
316 // If the window has been resized so that the image fits, restore the image size
317 // otherwise update the restored image size.
318 if (fitsInWindow)
319 restoreImageSize();
320 else
321 resizeImageToFit();
322 } else {
323 // If the image isn't resized but needs to be, then resize it.
324 if (!fitsInWindow) {
325 resizeImageToFit();
326 m_didShrinkImage = true;
327 }
328 }
329 }
330
cachedImage()331 CachedImage* ImageDocument::cachedImage()
332 {
333 if (!m_imageElement)
334 createDocumentStructure();
335
336 return m_imageElement->cachedImage();
337 }
338
shouldShrinkToFit() const339 bool ImageDocument::shouldShrinkToFit() const
340 {
341 return frame()->page()->settings()->shrinksStandaloneImagesToFit() &&
342 frame()->page()->mainFrame() == frame();
343 }
344
345 // --------
346
handleEvent(Event * event,bool)347 void ImageEventListener::handleEvent(Event* event, bool)
348 {
349 if (event->type() == eventNames().resizeEvent)
350 m_doc->windowSizeChanged();
351 else if (event->type() == eventNames().clickEvent) {
352 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
353 m_doc->imageClicked(mouseEvent->x(), mouseEvent->y());
354 }
355 }
356
357 // --------
358
~ImageDocumentElement()359 ImageDocumentElement::~ImageDocumentElement()
360 {
361 if (m_imageDocument)
362 m_imageDocument->disconnectImageElement();
363 }
364
willMoveToNewOwnerDocument()365 void ImageDocumentElement::willMoveToNewOwnerDocument()
366 {
367 if (m_imageDocument) {
368 m_imageDocument->disconnectImageElement();
369 m_imageDocument = 0;
370 }
371 HTMLImageElement::willMoveToNewOwnerDocument();
372 }
373
374 }
375