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