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 "ImageDocument.h"
     27 
     28 #include "CachedImage.h"
     29 #include "DocumentLoader.h"
     30 #include "EventListener.h"
     31 #include "EventNames.h"
     32 #include "Frame.h"
     33 #include "FrameLoaderClient.h"
     34 #include "FrameView.h"
     35 #include "HTMLHtmlElement.h"
     36 #include "HTMLImageElement.h"
     37 #include "HTMLNames.h"
     38 #include "LocalizedStrings.h"
     39 #include "MouseEvent.h"
     40 #include "NotImplemented.h"
     41 #include "Page.h"
     42 #include "RawDataDocumentParser.h"
     43 #include "Settings.h"
     44 
     45 using std::min;
     46 
     47 namespace WebCore {
     48 
     49 using namespace HTMLNames;
     50 
     51 class ImageEventListener : public EventListener {
     52 public:
     53     static PassRefPtr<ImageEventListener> create(ImageDocument* document) { return adoptRef(new ImageEventListener(document)); }
     54     static const ImageEventListener* cast(const EventListener* listener)
     55     {
     56         return listener->type() == ImageEventListenerType
     57             ? static_cast<const ImageEventListener*>(listener)
     58             : 0;
     59     }
     60 
     61     virtual bool operator==(const EventListener& other);
     62 
     63 private:
     64     ImageEventListener(ImageDocument* document)
     65         : EventListener(ImageEventListenerType)
     66         , m_doc(document)
     67     {
     68     }
     69 
     70     virtual void handleEvent(ScriptExecutionContext*, Event*);
     71 
     72     ImageDocument* m_doc;
     73 };
     74 
     75 class ImageDocumentParser : public RawDataDocumentParser {
     76 public:
     77     static PassRefPtr<ImageDocumentParser> create(ImageDocument* document)
     78     {
     79         return adoptRef(new ImageDocumentParser(document));
     80     }
     81 
     82     ImageDocument* document() const
     83     {
     84         return static_cast<ImageDocument*>(RawDataDocumentParser::document());
     85     }
     86 
     87 private:
     88     ImageDocumentParser(ImageDocument* document)
     89         : RawDataDocumentParser(document)
     90     {
     91     }
     92 
     93     virtual void appendBytes(DocumentWriter*, const char*, int, bool);
     94     virtual void finish();
     95 };
     96 
     97 class ImageDocumentElement : public HTMLImageElement {
     98 public:
     99     static PassRefPtr<ImageDocumentElement> create(ImageDocument*);
    100 
    101 private:
    102     ImageDocumentElement(ImageDocument* document)
    103         : HTMLImageElement(imgTag, document)
    104         , m_imageDocument(document)
    105     {
    106     }
    107 
    108     virtual ~ImageDocumentElement();
    109     virtual void willMoveToNewOwnerDocument();
    110 
    111     ImageDocument* m_imageDocument;
    112 };
    113 
    114 inline PassRefPtr<ImageDocumentElement> ImageDocumentElement::create(ImageDocument* document)
    115 {
    116     return adoptRef(new ImageDocumentElement(document));
    117 }
    118 
    119 // --------
    120 
    121 static float pageZoomFactor(const Document* document)
    122 {
    123     Frame* frame = document->frame();
    124     return frame ? frame->pageZoomFactor() : 1;
    125 }
    126 
    127 void ImageDocumentParser::appendBytes(DocumentWriter*, const char*, int, bool)
    128 {
    129     Frame* frame = document()->frame();
    130     Settings* settings = frame->settings();
    131     if (!frame->loader()->client()->allowImages(!settings || settings->areImagesEnabled()))
    132         return;
    133 
    134     CachedImage* cachedImage = document()->cachedImage();
    135     cachedImage->data(frame->loader()->documentLoader()->mainResourceData(), false);
    136 
    137     document()->imageUpdated();
    138 }
    139 
    140 void ImageDocumentParser::finish()
    141 {
    142     if (!isStopped() && document()->imageElement()) {
    143         CachedImage* cachedImage = document()->cachedImage();
    144         RefPtr<SharedBuffer> data = document()->frame()->loader()->documentLoader()->mainResourceData();
    145 
    146         // If this is a multipart image, make a copy of the current part, since the resource data
    147         // will be overwritten by the next part.
    148         if (document()->frame()->loader()->documentLoader()->isLoadingMultipartContent())
    149             data = data->copy();
    150 
    151         cachedImage->data(data.release(), true);
    152         cachedImage->finish();
    153 
    154         cachedImage->setResponse(document()->frame()->loader()->documentLoader()->response());
    155 
    156         // Report the natural image size in the page title, regardless of zoom
    157         // level.
    158         IntSize size = cachedImage->imageSize(1.0f);
    159         if (size.width()) {
    160             // Compute the title, we use the decoded filename of the resource, falling
    161             // back on the (decoded) hostname if there is no path.
    162             String fileName = decodeURLEscapeSequences(document()->url().lastPathComponent());
    163             if (fileName.isEmpty())
    164                 fileName = document()->url().host();
    165             document()->setTitle(imageTitle(fileName, size));
    166         }
    167 
    168         document()->imageUpdated();
    169     }
    170 
    171     document()->finishedParsing();
    172 }
    173 
    174 // --------
    175 
    176 ImageDocument::ImageDocument(Frame* frame, const KURL& url)
    177     : HTMLDocument(frame, url)
    178     , m_imageElement(0)
    179     , m_imageSizeIsKnown(false)
    180     , m_didShrinkImage(false)
    181     , m_shouldShrinkImage(shouldShrinkToFit())
    182 {
    183     setCompatibilityMode(QuirksMode);
    184     lockCompatibilityMode();
    185 }
    186 
    187 PassRefPtr<DocumentParser> ImageDocument::createParser()
    188 {
    189     return ImageDocumentParser::create(this);
    190 }
    191 
    192 void ImageDocument::createDocumentStructure()
    193 {
    194     ExceptionCode ec;
    195 
    196     RefPtr<Element> rootElement = Document::createElement(htmlTag, false);
    197     appendChild(rootElement, ec);
    198 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
    199     static_cast<HTMLHtmlElement*>(rootElement.get())->insertedByParser();
    200 #endif
    201 
    202     if (frame() && frame()->loader())
    203         frame()->loader()->dispatchDocumentElementAvailable();
    204 
    205     RefPtr<Element> body = Document::createElement(bodyTag, false);
    206     body->setAttribute(styleAttr, "margin: 0px;");
    207 
    208     rootElement->appendChild(body, ec);
    209 
    210     RefPtr<ImageDocumentElement> imageElement = ImageDocumentElement::create(this);
    211 
    212     imageElement->setAttribute(styleAttr, "-webkit-user-select: none");
    213     imageElement->setLoadManually(true);
    214     imageElement->setSrc(url().string());
    215 
    216     body->appendChild(imageElement, ec);
    217 
    218     if (shouldShrinkToFit()) {
    219         // Add event listeners
    220         RefPtr<EventListener> listener = ImageEventListener::create(this);
    221         if (DOMWindow* domWindow = this->domWindow())
    222             domWindow->addEventListener("resize", listener, false);
    223         imageElement->addEventListener("click", listener.release(), false);
    224     }
    225 
    226     m_imageElement = imageElement.get();
    227 }
    228 
    229 float ImageDocument::scale() const
    230 {
    231     if (!m_imageElement)
    232         return 1.0f;
    233 
    234     FrameView* view = frame()->view();
    235     if (!view)
    236         return 1;
    237 
    238     IntSize imageSize = m_imageElement->cachedImage()->imageSize(pageZoomFactor(this));
    239     IntSize windowSize = IntSize(view->width(), view->height());
    240 
    241     float widthScale = (float)windowSize.width() / imageSize.width();
    242     float heightScale = (float)windowSize.height() / imageSize.height();
    243 
    244     return min(widthScale, heightScale);
    245 }
    246 
    247 void ImageDocument::resizeImageToFit()
    248 {
    249     if (!m_imageElement)
    250         return;
    251 
    252     IntSize imageSize = m_imageElement->cachedImage()->imageSize(pageZoomFactor(this));
    253 
    254     float scale = this->scale();
    255     m_imageElement->setWidth(static_cast<int>(imageSize.width() * scale));
    256     m_imageElement->setHeight(static_cast<int>(imageSize.height() * scale));
    257 
    258     ExceptionCode ec;
    259     m_imageElement->style()->setProperty("cursor", "-webkit-zoom-in", ec);
    260 }
    261 
    262 void ImageDocument::imageClicked(int x, int y)
    263 {
    264     if (!m_imageSizeIsKnown || imageFitsInWindow())
    265         return;
    266 
    267     m_shouldShrinkImage = !m_shouldShrinkImage;
    268 
    269     if (m_shouldShrinkImage)
    270         windowSizeChanged();
    271     else {
    272         restoreImageSize();
    273 
    274         updateLayout();
    275 
    276         float scale = this->scale();
    277 
    278         int scrollX = static_cast<int>(x / scale - (float)frame()->view()->width() / 2);
    279         int scrollY = static_cast<int>(y / scale - (float)frame()->view()->height() / 2);
    280 
    281         frame()->view()->setScrollPosition(IntPoint(scrollX, scrollY));
    282     }
    283 }
    284 
    285 void ImageDocument::imageUpdated()
    286 {
    287     ASSERT(m_imageElement);
    288 
    289     if (m_imageSizeIsKnown)
    290         return;
    291 
    292     if (m_imageElement->cachedImage()->imageSize(pageZoomFactor(this)).isEmpty())
    293         return;
    294 
    295     m_imageSizeIsKnown = true;
    296 
    297     if (shouldShrinkToFit()) {
    298         // Force resizing of the image
    299         windowSizeChanged();
    300     }
    301 }
    302 
    303 void ImageDocument::restoreImageSize()
    304 {
    305     if (!m_imageElement || !m_imageSizeIsKnown)
    306         return;
    307 
    308     m_imageElement->setWidth(m_imageElement->cachedImage()->imageSize(pageZoomFactor(this)).width());
    309     m_imageElement->setHeight(m_imageElement->cachedImage()->imageSize(pageZoomFactor(this)).height());
    310 
    311     ExceptionCode ec;
    312     if (imageFitsInWindow())
    313         m_imageElement->style()->removeProperty("cursor", ec);
    314     else
    315         m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec);
    316 
    317     m_didShrinkImage = false;
    318 }
    319 
    320 bool ImageDocument::imageFitsInWindow() const
    321 {
    322     if (!m_imageElement)
    323         return true;
    324 
    325     FrameView* view = frame()->view();
    326     if (!view)
    327         return true;
    328 
    329     IntSize imageSize = m_imageElement->cachedImage()->imageSize(pageZoomFactor(this));
    330     IntSize windowSize = IntSize(view->width(), view->height());
    331 
    332     return imageSize.width() <= windowSize.width() && imageSize.height() <= windowSize.height();
    333 }
    334 
    335 void ImageDocument::windowSizeChanged()
    336 {
    337     if (!m_imageElement || !m_imageSizeIsKnown)
    338         return;
    339 
    340     bool fitsInWindow = imageFitsInWindow();
    341 
    342     // If the image has been explicitly zoomed in, restore the cursor if the image fits
    343     // and set it to a zoom out cursor if the image doesn't fit
    344     if (!m_shouldShrinkImage) {
    345         ExceptionCode ec;
    346 
    347         if (fitsInWindow)
    348             m_imageElement->style()->removeProperty("cursor", ec);
    349         else
    350             m_imageElement->style()->setProperty("cursor", "-webkit-zoom-out", ec);
    351         return;
    352     }
    353 
    354     if (m_didShrinkImage) {
    355         // If the window has been resized so that the image fits, restore the image size
    356         // otherwise update the restored image size.
    357         if (fitsInWindow)
    358             restoreImageSize();
    359         else
    360             resizeImageToFit();
    361     } else {
    362         // If the image isn't resized but needs to be, then resize it.
    363         if (!fitsInWindow) {
    364             resizeImageToFit();
    365             m_didShrinkImage = true;
    366         }
    367     }
    368 }
    369 
    370 CachedImage* ImageDocument::cachedImage()
    371 {
    372     if (!m_imageElement)
    373         createDocumentStructure();
    374 
    375     return m_imageElement->cachedImage();
    376 }
    377 
    378 bool ImageDocument::shouldShrinkToFit() const
    379 {
    380     return frame()->page()->settings()->shrinksStandaloneImagesToFit() &&
    381         frame()->page()->mainFrame() == frame();
    382 }
    383 
    384 // --------
    385 
    386 void ImageEventListener::handleEvent(ScriptExecutionContext*, Event* event)
    387 {
    388     if (event->type() == eventNames().resizeEvent)
    389         m_doc->windowSizeChanged();
    390     else if (event->type() == eventNames().clickEvent && event->isMouseEvent()) {
    391         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
    392         m_doc->imageClicked(mouseEvent->x(), mouseEvent->y());
    393     }
    394 }
    395 
    396 bool ImageEventListener::operator==(const EventListener& listener)
    397 {
    398     if (const ImageEventListener* imageEventListener = ImageEventListener::cast(&listener))
    399         return m_doc == imageEventListener->m_doc;
    400     return false;
    401 }
    402 
    403 // --------
    404 
    405 ImageDocumentElement::~ImageDocumentElement()
    406 {
    407     if (m_imageDocument)
    408         m_imageDocument->disconnectImageElement();
    409 }
    410 
    411 void ImageDocumentElement::willMoveToNewOwnerDocument()
    412 {
    413     if (m_imageDocument) {
    414         m_imageDocument->disconnectImageElement();
    415         m_imageDocument = 0;
    416     }
    417     HTMLImageElement::willMoveToNewOwnerDocument();
    418 }
    419 
    420 }
    421