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