1 /* 2 * Copyright (C) 2006, 2007, 2008 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 "CSSStyleDeclaration.h" 29 #include "CachedImage.h" 30 #include "DocumentLoader.h" 31 #include "Element.h" 32 #include "EventListener.h" 33 #include "EventNames.h" 34 #include "Frame.h" 35 #include "FrameLoader.h" 36 #include "FrameLoaderClient.h" 37 #include "FrameView.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 "SegmentedString.h" 45 #include "Settings.h" 46 #include "Text.h" 47 #include "XMLTokenizer.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 ImageTokenizer : public Tokenizer { 80 public: 81 ImageTokenizer(ImageDocument* doc) : m_doc(doc) {} 82 83 virtual void write(const SegmentedString&, bool appendData); 84 virtual void finish(); 85 virtual bool isWaitingForScripts() const; 86 87 virtual bool wantsRawData() const { return true; } 88 virtual bool writeRawData(const char* data, int len); 89 90 private: 91 ImageDocument* m_doc; 92 }; 93 94 class ImageDocumentElement : public HTMLImageElement { 95 public: 96 ImageDocumentElement(ImageDocument* doc) 97 : HTMLImageElement(imgTag, doc) 98 , m_imageDocument(doc) 99 { 100 } 101 102 virtual ~ImageDocumentElement(); 103 virtual void willMoveToNewOwnerDocument(); 104 105 private: 106 ImageDocument* m_imageDocument; 107 }; 108 109 // -------- 110 111 void ImageTokenizer::write(const SegmentedString&, bool) 112 { 113 // <https://bugs.webkit.org/show_bug.cgi?id=25397>: JS code can always call document.write, we need to handle it. 114 notImplemented(); 115 } 116 117 bool ImageTokenizer::writeRawData(const char*, int) 118 { 119 Frame* frame = m_doc->frame(); 120 Settings* settings = frame->settings(); 121 if (!frame->loader()->client()->allowImages(!settings || settings->areImagesEnabled())) 122 return false; 123 124 CachedImage* cachedImage = m_doc->cachedImage(); 125 cachedImage->data(frame->loader()->documentLoader()->mainResourceData(), false); 126 127 m_doc->imageChanged(); 128 129 return false; 130 } 131 132 void ImageTokenizer::finish() 133 { 134 if (!m_parserStopped && m_doc->imageElement()) { 135 CachedImage* cachedImage = m_doc->cachedImage(); 136 RefPtr<SharedBuffer> data = m_doc->frame()->loader()->documentLoader()->mainResourceData(); 137 138 // If this is a multipart image, make a copy of the current part, since the resource data 139 // will be overwritten by the next part. 140 if (m_doc->frame()->loader()->documentLoader()->isLoadingMultipartContent()) 141 data = data->copy(); 142 143 cachedImage->data(data.release(), true); 144 cachedImage->finish(); 145 146 cachedImage->setResponse(m_doc->frame()->loader()->documentLoader()->response()); 147 148 IntSize size = cachedImage->imageSize(m_doc->frame()->pageZoomFactor()); 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(m_doc->url().lastPathComponent()); 153 if (fileName.isEmpty()) 154 fileName = m_doc->url().host(); 155 m_doc->setTitle(imageTitle(fileName, size)); 156 } 157 158 m_doc->imageChanged(); 159 } 160 161 m_doc->finishedParsing(); 162 } 163 164 bool ImageTokenizer::isWaitingForScripts() const 165 { 166 // An image document is never waiting for scripts 167 return false; 168 } 169 170 // -------- 171 172 ImageDocument::ImageDocument(Frame* frame) 173 : HTMLDocument(frame) 174 , m_imageElement(0) 175 , m_imageSizeIsKnown(false) 176 , m_didShrinkImage(false) 177 , m_shouldShrinkImage(shouldShrinkToFit()) 178 { 179 setParseMode(Compat); 180 } 181 182 Tokenizer* ImageDocument::createTokenizer() 183 { 184 return new ImageTokenizer(this); 185 } 186 187 void ImageDocument::createDocumentStructure() 188 { 189 ExceptionCode ec; 190 191 RefPtr<Element> rootElement = Document::createElement(htmlTag, false); 192 appendChild(rootElement, ec); 193 194 RefPtr<Element> body = Document::createElement(bodyTag, false); 195 body->setAttribute(styleAttr, "margin: 0px;"); 196 197 rootElement->appendChild(body, ec); 198 199 RefPtr<ImageDocumentElement> imageElement = new ImageDocumentElement(this); 200 201 imageElement->setAttribute(styleAttr, "-webkit-user-select: none"); 202 imageElement->setLoadManually(true); 203 imageElement->setSrc(url().string()); 204 205 body->appendChild(imageElement, ec); 206 207 if (shouldShrinkToFit()) { 208 // Add event listeners 209 RefPtr<EventListener> listener = ImageEventListener::create(this); 210 if (DOMWindow* domWindow = this->domWindow()) 211 domWindow->addEventListener("resize", listener, false); 212 imageElement->addEventListener("click", listener.release(), false); 213 } 214 215 m_imageElement = imageElement.get(); 216 } 217 218 float ImageDocument::scale() const 219 { 220 if (!m_imageElement) 221 return 1.0f; 222 223 IntSize imageSize = m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()); 224 IntSize windowSize = IntSize(frame()->view()->width(), frame()->view()->height()); 225 226 float widthScale = (float)windowSize.width() / imageSize.width(); 227 float heightScale = (float)windowSize.height() / imageSize.height(); 228 229 return min(widthScale, heightScale); 230 } 231 232 void ImageDocument::resizeImageToFit() 233 { 234 if (!m_imageElement) 235 return; 236 237 IntSize imageSize = m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()); 238 239 float scale = this->scale(); 240 m_imageElement->setWidth(static_cast<int>(imageSize.width() * scale)); 241 m_imageElement->setHeight(static_cast<int>(imageSize.height() * scale)); 242 243 ExceptionCode ec; 244 m_imageElement->style()->setProperty("cursor", "-webkit-zoom-in", ec); 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::imageChanged() 271 { 272 ASSERT(m_imageElement); 273 274 if (m_imageSizeIsKnown) 275 return; 276 277 if (m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()).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) 291 return; 292 293 m_imageElement->setWidth(m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()).width()); 294 m_imageElement->setHeight(m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()).height()); 295 296 ExceptionCode ec; 297 if (imageFitsInWindow()) 298 m_imageElement->style()->removeProperty("cursor", ec); 299 else 300 m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec); 301 302 m_didShrinkImage = false; 303 } 304 305 bool ImageDocument::imageFitsInWindow() const 306 { 307 if (!m_imageElement) 308 return true; 309 310 IntSize imageSize = m_imageElement->cachedImage()->imageSize(frame()->pageZoomFactor()); 311 IntSize windowSize = IntSize(frame()->view()->width(), frame()->view()->height()); 312 313 return imageSize.width() <= windowSize.width() && imageSize.height() <= windowSize.height(); 314 } 315 316 void ImageDocument::windowSizeChanged() 317 { 318 if (!m_imageElement || !m_imageSizeIsKnown) 319 return; 320 321 bool fitsInWindow = imageFitsInWindow(); 322 323 // If the image has been explicitly zoomed in, restore the cursor if the image fits 324 // and set it to a zoom out cursor if the image doesn't fit 325 if (!m_shouldShrinkImage) { 326 ExceptionCode ec; 327 328 if (fitsInWindow) 329 m_imageElement->style()->removeProperty("cursor", ec); 330 else 331 m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec); 332 return; 333 } 334 335 if (m_didShrinkImage) { 336 // If the window has been resized so that the image fits, restore the image size 337 // otherwise update the restored image size. 338 if (fitsInWindow) 339 restoreImageSize(); 340 else 341 resizeImageToFit(); 342 } else { 343 // If the image isn't resized but needs to be, then resize it. 344 if (!fitsInWindow) { 345 resizeImageToFit(); 346 m_didShrinkImage = true; 347 } 348 } 349 } 350 351 CachedImage* ImageDocument::cachedImage() 352 { 353 if (!m_imageElement) 354 createDocumentStructure(); 355 356 return m_imageElement->cachedImage(); 357 } 358 359 bool ImageDocument::shouldShrinkToFit() const 360 { 361 return frame()->page()->settings()->shrinksStandaloneImagesToFit() && 362 frame()->page()->mainFrame() == frame(); 363 } 364 365 // -------- 366 367 void ImageEventListener::handleEvent(ScriptExecutionContext*, Event* event) 368 { 369 if (event->type() == eventNames().resizeEvent) 370 m_doc->windowSizeChanged(); 371 else if (event->type() == eventNames().clickEvent) { 372 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); 373 m_doc->imageClicked(mouseEvent->x(), mouseEvent->y()); 374 } 375 } 376 377 bool ImageEventListener::operator==(const EventListener& listener) 378 { 379 if (const ImageEventListener* imageEventListener = ImageEventListener::cast(&listener)) 380 return m_doc == imageEventListener->m_doc; 381 return false; 382 } 383 384 // -------- 385 386 ImageDocumentElement::~ImageDocumentElement() 387 { 388 if (m_imageDocument) 389 m_imageDocument->disconnectImageElement(); 390 } 391 392 void ImageDocumentElement::willMoveToNewOwnerDocument() 393 { 394 if (m_imageDocument) { 395 m_imageDocument->disconnectImageElement(); 396 m_imageDocument = 0; 397 } 398 HTMLImageElement::willMoveToNewOwnerDocument(); 399 } 400 401 } 402