Home | History | Annotate | Download | only in html
      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