• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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