2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@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.
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.
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.
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.
24 #include "core/html/HTMLImageElement.h"
26 #include "bindings/core/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/MediaValuesDynamic.h"
32 #include "core/css/parser/SizesAttributeParser.h"
33 #include "core/dom/Attribute.h"
34 #include "core/dom/NodeTraversal.h"
35 #include "core/fetch/ImageResource.h"
36 #include "core/frame/UseCounter.h"
37 #include "core/html/HTMLAnchorElement.h"
38 #include "core/html/HTMLCanvasElement.h"
39 #include "core/html/HTMLFormElement.h"
40 #include "core/html/HTMLSourceElement.h"
41 #include "core/html/canvas/CanvasRenderingContext.h"
42 #include "core/html/parser/HTMLParserIdioms.h"
43 #include "core/html/parser/HTMLSrcsetParser.h"
44 #include "core/inspector/ConsoleMessage.h"
45 #include "core/rendering/RenderImage.h"
46 #include "platform/MIMETypeRegistry.h"
47 #include "platform/RuntimeEnabledFeatures.h"
51 using namespace HTMLNames;
53 class HTMLImageElement::ViewportChangeListener FINAL : public MediaQueryListListener {
55 static RefPtrWillBeRawPtr<ViewportChangeListener> create(HTMLImageElement* element)
57 return adoptRefWillBeNoop(new ViewportChangeListener(element));
60 virtual void notifyMediaQueryChanged() OVERRIDE
63 m_element->notifyViewportChanged();
67 void clearElement() { m_element = nullptr; }
69 virtual void trace(Visitor* visitor) OVERRIDE
71 visitor->trace(m_element);
72 MediaQueryListListener::trace(visitor);
75 explicit ViewportChangeListener(HTMLImageElement* element) : m_element(element) { }
76 RawPtrWillBeMember<HTMLImageElement> m_element;
79 HTMLImageElement::HTMLImageElement(Document& document, HTMLFormElement* form, bool createdByParser)
80 : HTMLElement(imgTag, document)
81 , m_imageLoader(HTMLImageLoader::create(this))
82 , m_compositeOperator(CompositeSourceOver)
83 , m_imageDevicePixelRatio(1.0f)
84 , m_formWasSetByParser(false)
85 , m_elementCreatedByParser(createdByParser)
86 , m_intrinsicSizingViewportDependant(false)
87 , m_effectiveSizeViewportDependant(false)
89 if (form && form->inDocument()) {
93 m_form = form->createWeakPtr();
95 m_formWasSetByParser = true;
96 m_form->associate(*this);
97 m_form->didAssociateByParser();
101 PassRefPtrWillBeRawPtr<HTMLImageElement> HTMLImageElement::create(Document& document)
103 return adoptRefWillBeNoop(new HTMLImageElement(document));
106 PassRefPtrWillBeRawPtr<HTMLImageElement> HTMLImageElement::create(Document& document, HTMLFormElement* form, bool createdByParser)
108 return adoptRefWillBeNoop(new HTMLImageElement(document, form, createdByParser));
111 HTMLImageElement::~HTMLImageElement()
115 document().mediaQueryMatcher().removeViewportListener(m_listener.get());
116 m_listener->clearElement();
119 m_form->disassociate(*this);
123 void HTMLImageElement::trace(Visitor* visitor)
125 visitor->trace(m_imageLoader);
126 visitor->trace(m_listener);
127 visitor->trace(m_form);
128 HTMLElement::trace(visitor);
131 void HTMLImageElement::notifyViewportChanged()
133 // Re-selecting the source URL in order to pick a more fitting resource
134 // And update the image's intrinsic dimensions when the viewport changes.
135 // Picking of a better fitting resource is UA dependant, not spec required.
136 selectSourceURL(ImageLoader::UpdateSizeChanged);
139 PassRefPtrWillBeRawPtr<HTMLImageElement> HTMLImageElement::createForJSConstructor(Document& document, int width, int height)
141 RefPtrWillBeRawPtr<HTMLImageElement> image = adoptRefWillBeNoop(new HTMLImageElement(document));
143 image->setWidth(width);
145 image->setHeight(height);
146 image->m_elementCreatedByParser = false;
147 return image.release();
150 bool HTMLImageElement::isPresentationAttribute(const QualifiedName& name) const
152 if (name == widthAttr || name == heightAttr || name == borderAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == valignAttr)
154 return HTMLElement::isPresentationAttribute(name);
157 void HTMLImageElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
159 if (name == widthAttr)
160 addHTMLLengthToStyle(style, CSSPropertyWidth, value);
161 else if (name == heightAttr)
162 addHTMLLengthToStyle(style, CSSPropertyHeight, value);
163 else if (name == borderAttr)
164 applyBorderAttributeToStyle(value, style);
165 else if (name == vspaceAttr) {
166 addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
167 addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
168 } else if (name == hspaceAttr) {
169 addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
170 addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
171 } else if (name == alignAttr)
172 applyAlignmentAttributeToStyle(value, style);
173 else if (name == valignAttr)
174 addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
176 HTMLElement::collectStyleForPresentationAttribute(name, value, style);
179 const AtomicString HTMLImageElement::imageSourceURL() const
181 return m_bestFitImageURL.isNull() ? fastGetAttribute(srcAttr) : m_bestFitImageURL;
184 HTMLFormElement* HTMLImageElement::formOwner() const
189 void HTMLImageElement::formRemovedFromTree(const Node& formRoot)
192 if (NodeTraversal::highestAncestorOrSelf(*this) != formRoot)
196 void HTMLImageElement::resetFormOwner()
198 m_formWasSetByParser = false;
199 HTMLFormElement* nearestForm = findFormAncestor();
201 if (nearestForm == m_form.get())
203 m_form->disassociate(*this);
207 m_form = nearestForm;
209 m_form = nearestForm->createWeakPtr();
211 m_form->associate(*this);
216 m_form = WeakPtr<HTMLFormElement>();
221 void HTMLImageElement::setBestFitURLAndDPRFromImageCandidate(const ImageCandidate& candidate)
223 m_bestFitImageURL = candidate.url();
224 float candidateDensity = candidate.density();
225 if (candidateDensity >= 0)
226 m_imageDevicePixelRatio = 1.0 / candidateDensity;
227 if (candidate.resourceWidth() > 0) {
228 m_intrinsicSizingViewportDependant = true;
229 UseCounter::count(document(), UseCounter::SrcsetWDescriptor);
230 } else if (!candidate.srcOrigin()) {
231 UseCounter::count(document(), UseCounter::SrcsetXDescriptor);
233 if (renderer() && renderer()->isImage())
234 toRenderImage(renderer())->setImageDevicePixelRatio(m_imageDevicePixelRatio);
237 void HTMLImageElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
239 if (name == altAttr) {
240 if (renderer() && renderer()->isImage())
241 toRenderImage(renderer())->updateAltText();
242 } else if (name == srcAttr || name == srcsetAttr || name == sizesAttr) {
243 selectSourceURL(ImageLoader::UpdateIgnorePreviousError);
244 } else if (name == usemapAttr) {
245 setIsLink(!value.isNull());
246 } else if (name == compositeAttr) {
247 blink::WebBlendMode blendOp = blink::WebBlendModeNormal;
248 if (!parseCompositeAndBlendOperator(value, m_compositeOperator, blendOp))
249 m_compositeOperator = CompositeSourceOver;
250 else if (m_compositeOperator != CompositeSourceOver)
251 UseCounter::count(document(), UseCounter::HTMLImageElementComposite);
253 HTMLElement::parseAttribute(name, value);
257 const AtomicString& HTMLImageElement::altText() const
259 // lets figure out the alt text.. magic stuff
260 // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen
261 // also heavily discussed by Hixie on bugzilla
262 const AtomicString& alt = fastGetAttribute(altAttr);
265 // fall back to title attribute
266 return fastGetAttribute(titleAttr);
269 static bool supportedImageType(const String& type)
271 return MIMETypeRegistry::isSupportedImagePrefixedMIMEType(type);
274 // http://picture.responsiveimages.org/#update-source-set
275 ImageCandidate HTMLImageElement::findBestFitImageFromPictureParent()
277 ASSERT(isMainThread());
278 Node* parent = parentNode();
279 if (!parent || !isHTMLPictureElement(*parent))
280 return ImageCandidate();
281 for (Node* child = parent->firstChild(); child; child = child->nextSibling()) {
283 return ImageCandidate();
285 if (!isHTMLSourceElement(*child))
288 HTMLSourceElement* source = toHTMLSourceElement(child);
289 if (!source->fastGetAttribute(srcAttr).isNull())
290 UseCounter::countDeprecation(document(), UseCounter::PictureSourceSrc);
291 String srcset = source->fastGetAttribute(srcsetAttr);
292 if (srcset.isEmpty())
294 String type = source->fastGetAttribute(typeAttr);
295 if (!type.isEmpty() && !supportedImageType(type))
298 if (!source->mediaQueryMatches())
301 String sizes = source->fastGetAttribute(sizesAttr);
303 UseCounter::count(document(), UseCounter::Sizes);
304 SizesAttributeParser parser = SizesAttributeParser(MediaValuesDynamic::create(document()), sizes);
305 unsigned effectiveSize = parser.length();
306 m_effectiveSizeViewportDependant = parser.viewportDependant();
307 ImageCandidate candidate = bestFitSourceForSrcsetAttribute(document().devicePixelRatio(), effectiveSize, source->fastGetAttribute(srcsetAttr));
308 if (candidate.isEmpty())
312 return ImageCandidate();
315 RenderObject* HTMLImageElement::createRenderer(RenderStyle* style)
317 if (style->hasContent())
318 return RenderObject::createObject(this, style);
320 RenderImage* image = new RenderImage(this);
321 image->setImageResource(RenderImageResource::create());
322 image->setImageDevicePixelRatio(m_imageDevicePixelRatio);
326 bool HTMLImageElement::canStartSelection() const
329 return HTMLElement::canStartSelection();
334 void HTMLImageElement::attach(const AttachContext& context)
336 HTMLElement::attach(context);
338 if (renderer() && renderer()->isImage()) {
339 RenderImage* renderImage = toRenderImage(renderer());
340 RenderImageResource* renderImageResource = renderImage->imageResource();
341 if (renderImageResource->hasImage())
344 // If we have no image at all because we have no src attribute, set
345 // image height and width for the alt text instead.
346 if (!imageLoader().image() && !renderImageResource->cachedImage())
347 renderImage->setImageSizeForAltText();
349 renderImageResource->setImageResource(imageLoader().image());
354 Node::InsertionNotificationRequest HTMLImageElement::insertedInto(ContainerNode* insertionPoint)
356 if (!m_formWasSetByParser || NodeTraversal::highestAncestorOrSelf(*insertionPoint) != NodeTraversal::highestAncestorOrSelf(*m_form.get()))
359 document().mediaQueryMatcher().addViewportListener(m_listener);
361 bool imageWasModified = false;
362 if (RuntimeEnabledFeatures::pictureEnabled()) {
363 ImageCandidate candidate = findBestFitImageFromPictureParent();
364 if (!candidate.isEmpty()) {
365 setBestFitURLAndDPRFromImageCandidate(candidate);
366 imageWasModified = true;
370 // If we have been inserted from a renderer-less document,
371 // our loader may have not fetched the image, so do it now.
372 if ((insertionPoint->inDocument() && !imageLoader().image()) || imageWasModified)
373 imageLoader().updateFromElement(ImageLoader::UpdateNormal, m_elementCreatedByParser ? ImageLoader::ForceLoadImmediately : ImageLoader::LoadNormally);
375 return HTMLElement::insertedInto(insertionPoint);
378 void HTMLImageElement::removedFrom(ContainerNode* insertionPoint)
380 if (!m_form || NodeTraversal::highestAncestorOrSelf(*m_form.get()) != NodeTraversal::highestAncestorOrSelf(*this))
383 document().mediaQueryMatcher().removeViewportListener(m_listener);
384 HTMLElement::removedFrom(insertionPoint);
387 int HTMLImageElement::width(bool ignorePendingStylesheets)
390 // check the attribute first for an explicit pixel value
392 int width = getAttribute(widthAttr).toInt(&ok);
396 // if the image is available, use its width
397 if (imageLoader().image())
398 return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f).width();
401 if (ignorePendingStylesheets)
402 document().updateLayoutIgnorePendingStylesheets();
404 document().updateLayout();
406 RenderBox* box = renderBox();
407 return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedWidth(), box) : 0;
410 int HTMLImageElement::height(bool ignorePendingStylesheets)
413 // check the attribute first for an explicit pixel value
415 int height = getAttribute(heightAttr).toInt(&ok);
419 // if the image is available, use its height
420 if (imageLoader().image())
421 return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f).height();
424 if (ignorePendingStylesheets)
425 document().updateLayoutIgnorePendingStylesheets();
427 document().updateLayout();
429 RenderBox* box = renderBox();
430 return box ? adjustForAbsoluteZoom(box->contentBoxRect().pixelSnappedHeight(), box) : 0;
433 int HTMLImageElement::naturalWidth() const
435 if (!imageLoader().image())
438 return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f, ImageResource::IntrinsicSize).width();
441 int HTMLImageElement::naturalHeight() const
443 if (!imageLoader().image())
446 return imageLoader().image()->imageSizeForRenderer(renderer(), 1.0f, ImageResource::IntrinsicSize).height();
449 const String& HTMLImageElement::currentSrc() const
451 // http://www.whatwg.org/specs/web-apps/current-work/multipage/edits.html#dom-img-currentsrc
452 // The currentSrc IDL attribute must return the img element's current request's current URL.
453 // Initially, the pending request turns into current request when it is either available or broken.
454 // We use the image's dimensions as a proxy to it being in any of these states.
455 if (!imageLoader().image() || !imageLoader().image()->image() || !imageLoader().image()->image()->width())
458 return imageLoader().image()->url().string();
461 bool HTMLImageElement::isURLAttribute(const Attribute& attribute) const
463 return attribute.name() == srcAttr
464 || attribute.name() == lowsrcAttr
465 || attribute.name() == longdescAttr
466 || (attribute.name() == usemapAttr && attribute.value()[0] != '#')
467 || HTMLElement::isURLAttribute(attribute);
470 bool HTMLImageElement::hasLegalLinkAttribute(const QualifiedName& name) const
472 return name == srcAttr || HTMLElement::hasLegalLinkAttribute(name);
475 const QualifiedName& HTMLImageElement::subResourceAttributeName() const
480 bool HTMLImageElement::draggable() const
482 // Image elements are draggable by default.
483 return !equalIgnoringCase(getAttribute(draggableAttr), "false");
486 void HTMLImageElement::setHeight(int value)
488 setIntegralAttribute(heightAttr, value);
491 KURL HTMLImageElement::src() const
493 return document().completeURL(getAttribute(srcAttr));
496 void HTMLImageElement::setSrc(const String& value)
498 setAttribute(srcAttr, AtomicString(value));
501 void HTMLImageElement::setWidth(int value)
503 setIntegralAttribute(widthAttr, value);
506 int HTMLImageElement::x() const
508 document().updateLayoutIgnorePendingStylesheets();
509 RenderObject* r = renderer();
513 // FIXME: This doesn't work correctly with transforms.
514 FloatPoint absPos = r->localToAbsolute();
518 int HTMLImageElement::y() const
520 document().updateLayoutIgnorePendingStylesheets();
521 RenderObject* r = renderer();
525 // FIXME: This doesn't work correctly with transforms.
526 FloatPoint absPos = r->localToAbsolute();
530 bool HTMLImageElement::complete() const
532 return imageLoader().imageComplete();
535 void HTMLImageElement::didMoveToNewDocument(Document& oldDocument)
537 imageLoader().elementDidMoveToNewDocument();
538 HTMLElement::didMoveToNewDocument(oldDocument);
541 bool HTMLImageElement::isServerMap() const
543 if (!fastHasAttribute(ismapAttr))
546 const AtomicString& usemap = fastGetAttribute(usemapAttr);
548 // If the usemap attribute starts with '#', it refers to a map element in the document.
549 if (usemap[0] == '#')
552 return document().completeURL(stripLeadingAndTrailingHTMLSpaces(usemap)).isEmpty();
555 Image* HTMLImageElement::imageContents()
557 if (!imageLoader().imageComplete())
560 return imageLoader().image()->image();
563 bool HTMLImageElement::isInteractiveContent() const
565 return fastHasAttribute(usemapAttr);
568 PassRefPtr<Image> HTMLImageElement::getSourceImageForCanvas(SourceImageMode, SourceImageStatus* status) const
570 if (!complete() || !cachedImage()) {
571 *status = IncompleteSourceImageStatus;
575 if (cachedImage()->errorOccurred()) {
576 *status = UndecodableSourceImageStatus;
580 RefPtr<Image> sourceImage = cachedImage()->imageForRenderer(renderer());
582 // We need to synthesize a container size if a renderer is not available to provide one.
583 if (!renderer() && sourceImage->usesContainerSize())
584 sourceImage->setContainerSize(sourceImage->size());
586 *status = NormalSourceImageStatus;
587 return sourceImage->imageForDefaultFrame();
590 bool HTMLImageElement::wouldTaintOrigin(SecurityOrigin* destinationSecurityOrigin) const
592 ImageResource* image = cachedImage();
595 return !image->isAccessAllowed(destinationSecurityOrigin);
598 FloatSize HTMLImageElement::sourceSize() const
600 ImageResource* image = cachedImage();
604 size = image->imageSizeForRenderer(renderer(), 1.0f); // FIXME: Not sure about this.
609 FloatSize HTMLImageElement::defaultDestinationSize() const
611 ImageResource* image = cachedImage();
615 size = image->imageSizeForRenderer(renderer(), 1.0f); // FIXME: Not sure about this.
616 if (renderer() && renderer()->isRenderImage() && image->image() && !image->image()->hasRelativeWidth())
617 size.scale(toRenderImage(renderer())->imageDevicePixelRatio());
621 void HTMLImageElement::selectSourceURL(ImageLoader::UpdateFromElementBehavior behavior)
623 bool foundURL = false;
624 if (RuntimeEnabledFeatures::pictureEnabled()) {
625 ImageCandidate candidate = findBestFitImageFromPictureParent();
626 if (!candidate.isEmpty()) {
627 setBestFitURLAndDPRFromImageCandidate(candidate);
633 unsigned effectiveSize = 0;
634 if (RuntimeEnabledFeatures::pictureSizesEnabled()) {
635 String sizes = fastGetAttribute(sizesAttr);
637 UseCounter::count(document(), UseCounter::Sizes);
638 SizesAttributeParser parser = SizesAttributeParser(MediaValuesDynamic::create(document()), sizes);
639 effectiveSize = parser.length();
640 m_effectiveSizeViewportDependant = parser.viewportDependant();
642 ImageCandidate candidate = bestFitSourceForImageAttributes(document().devicePixelRatio(), effectiveSize, fastGetAttribute(srcAttr), fastGetAttribute(srcsetAttr));
643 setBestFitURLAndDPRFromImageCandidate(candidate);
645 if (m_intrinsicSizingViewportDependant && m_effectiveSizeViewportDependant && !m_listener) {
646 m_listener = ViewportChangeListener::create(this);
647 document().mediaQueryMatcher().addViewportListener(m_listener);
649 imageLoader().updateFromElement(behavior);
652 const KURL& HTMLImageElement::sourceURL() const
654 return cachedImage()->response().url();