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 "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