• 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 "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