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