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