2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 #include "ImageDocument.h"
28 #include "CachedImage.h"
29 #include "DocumentLoader.h"
31 #include "EventListener.h"
32 #include "EventNames.h"
34 #include "FrameLoader.h"
35 #include "FrameView.h"
36 #include "HTMLImageElement.h"
37 #include "HTMLNames.h"
38 #include "MouseEvent.h"
40 #include "SegmentedString.h"
43 #include "XMLTokenizer.h"
46 #include "ImageDocumentMac.h"
53 using namespace EventNames;
54 using namespace HTMLNames;
56 class ImageEventListener : public EventListener {
58 ImageEventListener(ImageDocument* doc) : m_doc(doc) { }
59 virtual void handleEvent(Event*, bool isWindowEvent);
65 class ImageTokenizer : public Tokenizer {
67 ImageTokenizer(ImageDocument* doc) : m_doc(doc) {}
69 virtual bool write(const SegmentedString&, bool appendData);
70 virtual void finish();
71 virtual bool isWaitingForScripts() const;
73 virtual bool wantsRawData() const { return true; }
74 virtual bool writeRawData(const char* data, int len);
80 class ImageDocumentElement : public HTMLImageElement {
82 ImageDocumentElement(ImageDocument* doc) : HTMLImageElement(doc), m_imageDocument(doc) { }
83 virtual ~ImageDocumentElement();
84 virtual void willMoveToNewOwnerDocument();
87 ImageDocument* m_imageDocument;
92 bool ImageTokenizer::write(const SegmentedString& s, bool appendData)
98 bool ImageTokenizer::writeRawData(const char* data, int len)
100 CachedImage* cachedImage = m_doc->cachedImage();
101 cachedImage->data(m_doc->frame()->loader()->documentLoader()->mainResourceData(), false);
103 m_doc->imageChanged();
108 void ImageTokenizer::finish()
110 if (!m_parserStopped && m_doc->imageElement()) {
111 CachedImage* cachedImage = m_doc->cachedImage();
112 RefPtr<SharedBuffer> data = m_doc->frame()->loader()->documentLoader()->mainResourceData();
114 // If this is a multipart image, make a copy of the current part, since the resource data
115 // will be overwritten by the next part.
116 if (m_doc->frame()->loader()->documentLoader()->isLoadingMultipartContent())
117 data = new SharedBuffer(data->data(), data->size());
119 cachedImage->data(data.release(), true);
120 cachedImage->finish();
122 cachedImage->setResponse(m_doc->frame()->loader()->documentLoader()->response());
124 // FIXME: Need code to set the title for platforms other than Mac OS X.
126 finishImageLoad(m_doc, cachedImage);
130 m_doc->finishedParsing();
133 bool ImageTokenizer::isWaitingForScripts() const
135 // An image document is never waiting for scripts
141 ImageDocument::ImageDocument(DOMImplementation* implementation, Frame* frame)
142 : HTMLDocument(implementation, frame)
144 , m_imageSizeIsKnown(false)
145 , m_didShrinkImage(false)
146 , m_shouldShrinkImage(shouldShrinkToFit())
148 setParseMode(Compat);
151 Tokenizer* ImageDocument::createTokenizer()
153 return new ImageTokenizer(this);
156 void ImageDocument::createDocumentStructure()
160 RefPtr<Element> rootElement = createElementNS(xhtmlNamespaceURI, "html", ec);
161 appendChild(rootElement, ec);
163 RefPtr<Element> body = createElementNS(xhtmlNamespaceURI, "body", ec);
164 body->setAttribute(styleAttr, "margin: 0px;");
166 rootElement->appendChild(body, ec);
168 RefPtr<ImageDocumentElement> imageElement = new ImageDocumentElement(this);
170 imageElement->setAttribute(styleAttr, "-webkit-user-select: none");
171 imageElement->setLoadManually(true);
172 imageElement->setSrc(URL());
174 body->appendChild(imageElement, ec);
176 if (shouldShrinkToFit()) {
177 // Add event listeners
178 RefPtr<EventListener> listener = new ImageEventListener(this);
179 addWindowEventListener("resize", listener, false);
180 imageElement->addEventListener("click", listener.release(), false);
183 m_imageElement = imageElement.get();
186 float ImageDocument::scale() const
191 IntSize imageSize = m_imageElement->cachedImage()->imageSize();
192 IntSize windowSize = IntSize(frame()->view()->width(), frame()->view()->height());
194 float widthScale = (float)windowSize.width() / imageSize.width();
195 float heightScale = (float)windowSize.height() / imageSize.height();
197 return min(widthScale, heightScale);
200 void ImageDocument::resizeImageToFit()
205 IntSize imageSize = m_imageElement->cachedImage()->imageSize();
207 float scale = this->scale();
208 m_imageElement->setWidth(static_cast<int>(imageSize.width() * scale));
209 m_imageElement->setHeight(static_cast<int>(imageSize.height() * scale));
212 m_imageElement->style()->setProperty("cursor", "-webkit-zoom-in", ec);
215 void ImageDocument::imageClicked(int x, int y)
217 if (!m_imageSizeIsKnown || imageFitsInWindow())
220 m_shouldShrinkImage = !m_shouldShrinkImage;
222 if (m_shouldShrinkImage)
229 float scale = this->scale();
231 int scrollX = static_cast<int>(x / scale - (float)frame()->view()->width() / 2);
232 int scrollY = static_cast<int>(y / scale - (float)frame()->view()->height() / 2);
234 frame()->view()->setContentsPos(scrollX, scrollY);
238 void ImageDocument::imageChanged()
240 ASSERT(m_imageElement);
242 if (m_imageSizeIsKnown)
245 if (m_imageElement->cachedImage()->imageSize().isEmpty())
248 m_imageSizeIsKnown = true;
250 if (shouldShrinkToFit()) {
251 // Force resizing of the image
256 void ImageDocument::restoreImageSize()
258 if (!m_imageElement || !m_imageSizeIsKnown)
261 m_imageElement->setWidth(m_imageElement->cachedImage()->imageSize().width());
262 m_imageElement->setHeight(m_imageElement->cachedImage()->imageSize().height());
265 if (imageFitsInWindow())
266 m_imageElement->style()->removeProperty("cursor", ec);
268 m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec);
270 m_didShrinkImage = false;
273 bool ImageDocument::imageFitsInWindow() const
278 IntSize imageSize = m_imageElement->cachedImage()->imageSize();
279 IntSize windowSize = IntSize(frame()->view()->width(), frame()->view()->height());
281 return imageSize.width() <= windowSize.width() && imageSize.height() <= windowSize.height();
284 void ImageDocument::windowSizeChanged()
286 if (!m_imageElement || !m_imageSizeIsKnown)
289 bool fitsInWindow = imageFitsInWindow();
291 // If the image has been explicitly zoomed in, restore the cursor if the image fits
292 // and set it to a zoom out cursor if the image doesn't fit
293 if (!m_shouldShrinkImage) {
297 m_imageElement->style()->removeProperty("cursor", ec);
299 m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec);
303 if (m_didShrinkImage) {
304 // If the window has been resized so that the image fits, restore the image size
305 // otherwise update the restored image size.
311 // If the image isn't resized but needs to be, then resize it.
314 m_didShrinkImage = true;
319 CachedImage* ImageDocument::cachedImage()
322 createDocumentStructure();
324 return m_imageElement->cachedImage();
327 bool ImageDocument::shouldShrinkToFit() const
329 return frame()->page()->settings()->shrinksStandaloneImagesToFit() &&
330 frame()->page()->mainFrame() == frame();
335 void ImageEventListener::handleEvent(Event* event, bool isWindowEvent)
337 if (event->type() == resizeEvent)
338 m_doc->windowSizeChanged();
339 else if (event->type() == clickEvent) {
340 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
341 m_doc->imageClicked(mouseEvent->x(), mouseEvent->y());
347 ImageDocumentElement::~ImageDocumentElement()
350 m_imageDocument->disconnectImageElement();
353 void ImageDocumentElement::willMoveToNewOwnerDocument()
355 if (m_imageDocument) {
356 m_imageDocument->disconnectImageElement();
359 HTMLImageElement::willMoveToNewOwnerDocument();