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