Home | History | Annotate | Download | only in html
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights reserved.
      5  * Copyright (C) 2010 Google Inc. All rights reserved.
      6  *
      7  * This library is free software; you can redistribute it and/or
      8  * modify it under the terms of the GNU Library General Public
      9  * License as published by the Free Software Foundation; either
     10  * version 2 of the License, or (at your option) any later version.
     11  *
     12  * This library is distributed in the hope that it will be useful,
     13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     15  * Library General Public License for more details.
     16  *
     17  * You should have received a copy of the GNU Library General Public License
     18  * along with this library; see the file COPYING.LIB.  If not, write to
     19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     20  * Boston, MA 02110-1301, USA.
     21  */
     22 
     23 #include "config.h"
     24 #include "core/html/HTMLImageElement.h"
     25 
     26 #include "bindings/v8/ScriptEventListener.h"
     27 #include "core/CSSPropertyNames.h"
     28 #include "core/HTMLNames.h"
     29 #include "core/MediaTypeNames.h"
     30 #include "core/css/MediaQueryMatcher.h"
     31 #include "core/css/MediaValuesCached.h"
     32 #include "core/css/parser/SizesAttributeParser.h"
     33 #include "core/dom/Attribute.h"
     34 #include "core/fetch/ImageResource.h"
     35 #include "core/html/HTMLAnchorElement.h"
     36 #include "core/html/HTMLCanvasElement.h"
     37 #include "core/html/HTMLFormElement.h"
     38 #include "core/html/HTMLSourceElement.h"
     39 #include "core/html/canvas/CanvasRenderingContext.h"
     40 #include "core/html/parser/HTMLParserIdioms.h"
     41 #include "core/html/parser/HTMLSrcsetParser.h"
     42 #include "core/rendering/RenderImage.h"
     43 #include "platform/MIMETypeRegistry.h"
     44 #include "platform/RuntimeEnabledFeatures.h"
     45 
     46 namespace WebCore {
     47 
     48 using namespace HTMLNames;
     49 
     50 HTMLImageElement::HTMLImageElement(Document& document, HTMLFormElement* form, bool createdByParser)
     51     : HTMLElement(imgTag, document)
     52     , m_imageLoader(HTMLImageLoader::create(this))
     53     , m_compositeOperator(CompositeSourceOver)
     54     , m_imageDevicePixelRatio(1.0f)
     55     , m_formWasSetByParser(false)
     56     , m_elementCreatedByParser(createdByParser)
     57 {
     58     ScriptWrappable::init(this);
     59     if (form && form->inDocument()) {
     60 #if ENABLE(OILPAN)
     61         m_form = form;
     62 #else
     63         m_form = form->createWeakPtr();
     64 #endif
     65         m_formWasSetByParser = true;
     66         m_form->associate(*this);
     67         m_form->didAssociateByParser();
     68     }
     69 }
     70 
     71 PassRefPtrWillBeRawPtr<HTMLImageElement> HTMLImageElement::create(Document& document)
     72 {
     73     return adoptRefWillBeNoop(new HTMLImageElement(document));
     74 }
     75 
     76 PassRefPtrWillBeRawPtr<HTMLImageElement> HTMLImageElement::create(Document& document, HTMLFormElement* form, bool createdByParser)
     77 {
     78     return adoptRefWillBeNoop(new HTMLImageElement(document, form, createdByParser));
     79 }
     80 
     81 HTMLImageElement::~HTMLImageElement()
     82 {
     83 #if !ENABLE(OILPAN)
     84     if (m_form)
     85         m_form->disassociate(*this);
     86 #endif
     87 }
     88 
     89 void HTMLImageElement::trace(Visitor* visitor)
     90 {
     91     visitor->trace(m_imageLoader);
     92     visitor->trace(m_form);
     93     HTMLElement::trace(visitor);
     94 }
     95 
     96 PassRefPtrWillBeRawPtr<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, int width, int height)
     97 {
     98     RefPtrWillBeRawPtr<HTMLImageElement> image = adoptRefWillBeNoop(new HTMLImageElement(document));
     99     if (width)
    100         image->setWidth(width);
    101     if (height)
    102         image->setHeight(height);
    103     image->m_elementCreatedByParser = false;
    104     return image.release();
    105 }
    106 
    107 bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const
    108 {
    109     if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == valignAttr)
    110         return true;
    111     return HTMLElement::isPresentationAttribute(name);
    112 }
    113 
    114 void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
    115 {
    116     if (name == widthAttr)
    117         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
    118     else if (name == heightAttr)
    119         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
    120     else if (name == borderAttr)
    121         applyBorderAttributeToStyle(value, style);
    122     else if (name == vspaceAttr) {
    123         addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
    124         addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
    125     } else if (name == hspaceAttr) {
    126         addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
    127         addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
    128     } else if (name == alignAttr)
    129         applyAlignmentAttributeToStyle(value, style);
    130     else if (name == valignAttr)
    131         addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
    132     else
    133         HTMLElement::collectStyleForPresentationAttribute(name, value, style);
    134 }
    135 
    136 const AtomicString HTMLImageElement::imageSourceURL() const
    137 {
    138     return m_bestFitImageURL.isNull() ? fastGetAttribute(srcAttr) : m_bestFitImageURL;
    139 }
    140 
    141 HTMLFormElement* HTMLImageElement::formOwner() const
    142 {
    143     return m_form.get();
    144 }
    145 
    146 void HTMLImageElement::formRemovedFromTree(const Node& formRoot)
    147 {
    148     ASSERT(m_form);
    149     if (highestAncestorOrSelf() != formRoot)
    150         resetFormOwner();
    151 }
    152 
    153 void HTMLImageElement::resetFormOwner()
    154 {
    155     m_formWasSetByParser = false;
    156     HTMLFormElement* nearestForm = findFormAncestor();
    157     if (m_form) {
    158         if (nearestForm == m_form.get())
    159             return;
    160         m_form->disassociate(*this);
    161     }
    162     if (nearestForm) {
    163 #if ENABLE(OILPAN)
    164         m_form = nearestForm;
    165 #else
    166         m_form = nearestForm->createWeakPtr();
    167 #endif
    168         m_form->associate(*this);
    169     } else {
    170 #if ENABLE(OILPAN)
    171         m_form = nullptr;
    172 #else
    173         m_form = WeakPtr<HTMLFormElement>();
    174 #endif
    175     }
    176 }
    177 
    178 void HTMLImageElement::setBestFitURLAndDPRFromImageCandidate(const ImageCandidate& candidate)
    179 {
    180     m_bestFitImageURL = candidate.url();
    181     float candidateDensity = candidate.density();
    182     if (candidateDensity >= 0)
    183         m_imageDevicePixelRatio = 1.0 / candidateDensity;
    184     if (renderer() && renderer()->isImage())
    185         toRenderImage(renderer())->setImageDevicePixelRatio(m_imageDevicePixelRatio);
    186 }
    187 
    188 void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
    189 {
    190     if (name == altAttr) {
    191         if (renderer() && renderer()->isImage())
    192             toRenderImage(renderer())->updateAltText();
    193     } else if (name == srcAttr || name == srcsetAttr || name == sizesAttr) {
    194         selectSourceURL(UpdateIgnorePreviousError);
    195     } else if (name == usemapAttr) {
    196         setIsLink(!value.isNull());
    197     } else if (name == compositeAttr) {
    198         // FIXME: images don't support blend modes in their compositing attribute.
    199         blink::WebBlendMode blendOp = blink::WebBlendModeNormal;
    200         if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
    201             m_compositeOperator = CompositeSourceOver;
    202     } else {
    203         HTMLElement::parseAttribute(name, value);
    204     }
    205 }
    206 
    207 const AtomicString& HTMLImageElement::altText() const
    208 {
    209     // lets figure out the alt text.. magic stuff
    210     // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
    211     // also heavily discussed by Hixie on bugzilla
    212     const AtomicString& alt = fastGetAttribute(altAttr);
    213     if (!alt.isNull())
    214         return alt;
    215     // fall back to title attribute
    216     return fastGetAttribute(titleAttr);
    217 }
    218 
    219 static bool supportedImageType(const String& type)
    220 {
    221     return MIMETypeRegistry::isSupportedImageResourceMIMEType(type);
    222 }
    223 
    224 // http://picture.responsiveimages.org/#update-source-set
    225 ImageCandidate HTMLImageElement::findBestFitImageFromPictureParent()
    226 {
    227     ASSERT(isMainThread());
    228     Node* parent = parentNode();
    229     if (!parent || !isHTMLPictureElement(*parent))
    230         return ImageCandidate();
    231     for (Node* child = parent->firstChild(); child; child = child->nextSibling()) {
    232         if (child == this)
    233             return ImageCandidate();
    234 
    235         if (!isHTMLSourceElement(*child))
    236             continue;
    237 
    238         HTMLSourceElement* source = toHTMLSourceElement(child);
    239         String srcset = source->fastGetAttribute(srcsetAttr);
    240         if (srcset.isEmpty())
    241             continue;
    242         String type = source->fastGetAttribute(typeAttr);
    243         if (!type.isEmpty() && !supportedImageType(type))
    244             continue;
    245 
    246         String media = source->fastGetAttribute(mediaAttr);
    247         if (!media.isEmpty()) {
    248             RefPtrWillBeRawPtr<MediaQuerySet> mediaQueries = MediaQuerySet::create(media);
    249             if (!document().mediaQueryMatcher().evaluate(mediaQueries.get()))
    250                 continue;
    251         }
    252 
    253         unsigned effectiveSize = SizesAttributeParser::findEffectiveSize(source->fastGetAttribute(sizesAttr), MediaValuesCached::create(document()));
    254         ImageCandidate candidate = bestFitSourceForSrcsetAttribute(document().devicePixelRatio(), effectiveSize, source->fastGetAttribute(srcsetAttr));
    255         if (candidate.isEmpty())
    256             continue;
    257         return candidate;
    258     }
    259     return ImageCandidate();
    260 }
    261 
    262 RenderObject* HTMLImageElement::createRenderer(RenderStyle* style)
    263 {
    264     if (style->hasContent())
    265         return RenderObject::createObject(this, style);
    266 
    267     RenderImage* image = new RenderImage(this);
    268     image->setImageResource(RenderImageResource::create());
    269     image->setImageDevicePixelRatio(m_imageDevicePixelRatio);
    270     return image;
    271 }
    272 
    273 bool HTMLImageElement::canStartSelection() const
    274 {
    275     if (shadow())
    276         return HTMLElement::canStartSelection();
    277 
    278     return false;
    279 }
    280 
    281 void HTMLImageElement::attach(const AttachContext& context)
    282 {
    283     HTMLElement::attach(context);
    284 
    285     if (renderer() && renderer()->isImage()) {
    286         RenderImage* renderImage = toRenderImage(renderer());
    287         RenderImageResource* renderImageResource = renderImage->imageResource();
    288         if (renderImageResource->hasImage())
    289             return;
    290 
    291         // If we have no image at all because we have no src attribute, set
    292         // image height and width for the alt text instead.
    293         if (!imageLoader().image() && !renderImageResource->cachedImage())
    294             renderImage->setImageSizeForAltText();
    295         else
    296             renderImageResource->setImageResource(imageLoader().image());
    297 
    298     }
    299 }
    300 
    301 Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode* insertionPoint)
    302 {
    303     if (!m_formWasSetByParser || insertionPoint->highestAncestorOrSelf() != m_form->highestAncestorOrSelf())
    304         resetFormOwner();
    305 
    306     bool imageWasModified = false;
    307     if (RuntimeEnabledFeatures::pictureEnabled()) {
    308         ImageCandidate candidate = findBestFitImageFromPictureParent();
    309         if (!candidate.isEmpty()) {
    310             setBestFitURLAndDPRFromImageCandidate(candidate);
    311             imageWasModified = true;
    312         }
    313     }
    314 
    315     // If we have been inserted from a renderer-less document,
    316     // our loader may have not fetched the image, so do it now.
    317     if ((insertionPoint->inDocument() && !imageLoader().image()) || imageWasModified)
    318         imageLoader().updateFromElement(m_elementCreatedByParser ? ImageLoader::ForceLoadImmediately : ImageLoader::LoadNormally);
    319 
    320     return HTMLElement::insertedInto(insertionPoint);
    321 }
    322 
    323 void HTMLImageElement::removedFrom(ContainerNode* insertionPoint)
    324 {
    325     if (!m_form || m_form->highestAncestorOrSelf() != highestAncestorOrSelf())
    326         resetFormOwner();
    327     HTMLElement::removedFrom(insertionPoint);
    328 }
    329 
    330 int HTMLImageElement::width(bool ignorePendingStylesheets)
    331 {
    332     if (!renderer()) {
    333         // check the attribute first for an explicit pixel value
    334         bool ok;
    335         int width = getAttribute(widthAttr).toInt(&ok);
    336         if (ok)
    337             return width;
    338 
    339         // if the image is available, use its width
    340         if (imageLoader().image())
    341             return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f).width();
    342     }
    343 
    344     if (ignorePendingStylesheets)
    345         document().updateLayoutIgnorePendingStylesheets();
    346     else
    347         document().updateLayout();
    348 
    349     RenderBox* box = renderBox();
    350     return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedWidth(), box) : 0;
    351 }
    352 
    353 int HTMLImageElement::height(bool ignorePendingStylesheets)
    354 {
    355     if (!renderer()) {
    356         // check the attribute first for an explicit pixel value
    357         bool ok;
    358         int height = getAttribute(heightAttr).toInt(&ok);
    359         if (ok)
    360             return height;
    361 
    362         // if the image is available, use its height
    363         if (imageLoader().image())
    364             return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f).height();
    365     }
    366 
    367     if (ignorePendingStylesheets)
    368         document().updateLayoutIgnorePendingStylesheets();
    369     else
    370         document().updateLayout();
    371 
    372     RenderBox* box = renderBox();
    373     return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedHeight(), box) : 0;
    374 }
    375 
    376 int HTMLImageElement::naturalWidth() const
    377 {
    378     if (!imageLoader().image())
    379         return 0;
    380 
    381     return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f).width();
    382 }
    383 
    384 int HTMLImageElement::naturalHeight() const
    385 {
    386     if (!imageLoader().image())
    387         return 0;
    388 
    389     return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f).height();
    390 }
    391 
    392 const String& HTMLImageElement::currentSrc() const
    393 {
    394     // http://www.whatwg.org/specs/web-apps/current-work/multipage/edits.html#dom-img-currentsrc
    395     // The currentSrc IDL attribute must return the img element's current request's current URL.
    396     // Initially, the pending request turns into current request when it is either available or broken.
    397     // We use the image's dimensions as a proxy to it being in any of these states.
    398     if (!imageLoader().image() || !imageLoader().image()->image() || !imageLoader().image()->image()->width())
    399         return emptyAtom;
    400 
    401     return imageLoader().image()->url().string();
    402 }
    403 
    404 bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
    405 {
    406     return attribute.name() == srcAttr
    407         || attribute.name() == lowsrcAttr
    408         || attribute.name() == longdescAttr
    409         || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#')
    410         || HTMLElement::isURLAttribute(attribute);
    411 }
    412 
    413 bool HTMLImageElement::hasLegalLinkAttribute(const QualifiedName& name) const
    414 {
    415     return name == srcAttr || HTMLElement::hasLegalLinkAttribute(name);
    416 }
    417 
    418 const QualifiedName& HTMLImageElement::subResourceAttributeName() const
    419 {
    420     return srcAttr;
    421 }
    422 
    423 const AtomicString& HTMLImageElement::alt() const
    424 {
    425     return fastGetAttribute(altAttr);
    426 }
    427 
    428 bool HTMLImageElement::draggable() const
    429 {
    430     // Image elements are draggable by default.
    431     return !equalIgnoringCase(getAttribute(draggableAttr), "false");
    432 }
    433 
    434 void HTMLImageElement::setHeight(int value)
    435 {
    436     setIntegralAttribute(heightAttr, value);
    437 }
    438 
    439 KURL HTMLImageElement::src() const
    440 {
    441     return document().completeURL(getAttribute(srcAttr));
    442 }
    443 
    444 void HTMLImageElement::setSrc(const String& value)
    445 {
    446     setAttribute(srcAttr, AtomicString(value));
    447 }
    448 
    449 void HTMLImageElement::setWidth(int value)
    450 {
    451     setIntegralAttribute(widthAttr, value);
    452 }
    453 
    454 int HTMLImageElement::x() const
    455 {
    456     RenderObject* r = renderer();
    457     if (!r)
    458         return 0;
    459 
    460     // FIXME: This doesn't work correctly with transforms.
    461     FloatPoint absPos = r->localToAbsolute();
    462     return absPos.x();
    463 }
    464 
    465 int HTMLImageElement::y() const
    466 {
    467     RenderObject* r = renderer();
    468     if (!r)
    469         return 0;
    470 
    471     // FIXME: This doesn't work correctly with transforms.
    472     FloatPoint absPos = r->localToAbsolute();
    473     return absPos.y();
    474 }
    475 
    476 bool HTMLImageElement::complete() const
    477 {
    478     return imageLoader().imageComplete();
    479 }
    480 
    481 void HTMLImageElement::didMoveToNewDocument(Document& oldDocument)
    482 {
    483     imageLoader().elementDidMoveToNewDocument();
    484     HTMLElement::didMoveToNewDocument(oldDocument);
    485 }
    486 
    487 bool HTMLImageElement::isServerMap() const
    488 {
    489     if (!fastHasAttribute(ismapAttr))
    490         return false;
    491 
    492     const AtomicString& usemap = fastGetAttribute(usemapAttr);
    493 
    494     // If the usemap attribute starts with '#', it refers to a map element in the document.
    495     if (usemap.string()[0] == '#')
    496         return false;
    497 
    498     return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
    499 }
    500 
    501 Image* HTMLImageElement::imageContents()
    502 {
    503     if (!imageLoader().imageComplete())
    504         return 0;
    505 
    506     return imageLoader().image()->image();
    507 }
    508 
    509 bool HTMLImageElement::isInteractiveContent() const
    510 {
    511     return fastHasAttribute(usemapAttr);
    512 }
    513 
    514 PassRefPtr<Image> HTMLImageElement::getSourceImageForCanvas(SourceImageMode, SourceImageStatus* status) const
    515 {
    516     if (!complete() || !cachedImage()) {
    517         *status = IncompleteSourceImageStatus;
    518         return nullptr;
    519     }
    520 
    521     if (cachedImage()->errorOccurred()) {
    522         *status = UndecodableSourceImageStatus;
    523         return nullptr;
    524     }
    525 
    526     RefPtr<Image> sourceImage = cachedImage()->imageForRenderer(renderer());
    527 
    528     // We need to synthesize a container size if a renderer is not available to provide one.
    529     if (!renderer() && sourceImage->usesContainerSize())
    530         sourceImage->setContainerSize(sourceImage->size());
    531 
    532     *status = NormalSourceImageStatus;
    533     return sourceImage.release();
    534 }
    535 
    536 bool HTMLImageElement::wouldTaintOrigin(SecurityOrigin* destinationSecurityOrigin) const
    537 {
    538     ImageResource* image = cachedImage();
    539     if (!image)
    540         return false;
    541     return !image->isAccessAllowed(destinationSecurityOrigin);
    542 }
    543 
    544 FloatSize HTMLImageElement::sourceSize() const
    545 {
    546     ImageResource* image = cachedImage();
    547     if (!image)
    548         return FloatSize();
    549     LayoutSize size;
    550     size = image->imageSizeForRenderer(renderer(), 1.0f); // FIXME: Not sure about this.
    551 
    552     return size;
    553 }
    554 
    555 FloatSize HTMLImageElement::defaultDestinationSize() const
    556 {
    557     ImageResource* image = cachedImage();
    558     if (!image)
    559         return FloatSize();
    560     LayoutSize size;
    561     size = image->imageSizeForRenderer(renderer(), 1.0f); // FIXME: Not sure about this.
    562     if (renderer() && renderer()->isRenderImage() && image->image() && !image->image()->hasRelativeWidth())
    563         size.scale(toRenderImage(renderer())->imageDevicePixelRatio());
    564     return size;
    565 }
    566 
    567 void HTMLImageElement::selectSourceURL(UpdateFromElementBehavior behavior)
    568 {
    569     bool foundURL = false;
    570     if (RuntimeEnabledFeatures::pictureEnabled()) {
    571         ImageCandidate candidate = findBestFitImageFromPictureParent();
    572         if (!candidate.isEmpty()) {
    573             setBestFitURLAndDPRFromImageCandidate(candidate);
    574             foundURL = true;
    575         }
    576     }
    577 
    578     if (!foundURL) {
    579         unsigned effectiveSize = 0;
    580         if (RuntimeEnabledFeatures::pictureSizesEnabled())
    581             effectiveSize = SizesAttributeParser::findEffectiveSize(fastGetAttribute(sizesAttr), MediaValuesCached::create(document()));
    582         ImageCandidate candidate = bestFitSourceForImageAttributes(document().devicePixelRatio(), effectiveSize, fastGetAttribute(srcAttr), fastGetAttribute(srcsetAttr));
    583         setBestFitURLAndDPRFromImageCandidate(candidate);
    584     }
    585     if (behavior == UpdateIgnorePreviousError)
    586         imageLoader().updateFromElementIgnoringPreviousError();
    587     else
    588         imageLoader().updateFromElement();
    589 }
    590 
    591 const KURL& HTMLImageElement::sourceURL() const
    592 {
    593     return cachedImage()->response().url();
    594 }
    595 
    596 }
    597