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