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