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