1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2000 Stefan Schimanski (1Stein (at) gmx.de) 5 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2011 Apple Inc. All rights reserved. 6 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) 7 * 8 * This library is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU Library General Public 10 * License as published by the Free Software Foundation; either 11 * version 2 of the License, or (at your option) any later version. 12 * 13 * This library is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU Library General Public License 19 * along with this library; see the file COPYING.LIB. If not, write to 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 * Boston, MA 02110-1301, USA. 22 */ 23 24 #include "config.h" 25 #include "HTMLObjectElement.h" 26 27 #include "Attribute.h" 28 #include "CSSValueKeywords.h" 29 #include "EventNames.h" 30 #include "ExceptionCode.h" 31 #include "Frame.h" 32 #include "HTMLDocument.h" 33 #include "HTMLFormElement.h" 34 #include "HTMLImageLoader.h" 35 #include "HTMLNames.h" 36 #include "HTMLParamElement.h" 37 #include "HTMLParserIdioms.h" 38 #include "MIMETypeRegistry.h" 39 #include "RenderEmbeddedObject.h" 40 #include "RenderImage.h" 41 #include "RenderWidget.h" 42 #include "ScriptController.h" 43 #include "ScriptEventListener.h" 44 #include "Text.h" 45 46 namespace WebCore { 47 48 using namespace HTMLNames; 49 50 inline HTMLObjectElement::HTMLObjectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser) 51 : HTMLPlugInImageElement(tagName, document, createdByParser, ShouldNotPreferPlugInsForImages) 52 , FormAssociatedElement(form) 53 , m_docNamedItem(true) 54 , m_useFallbackContent(false) 55 { 56 ASSERT(hasTagName(objectTag)); 57 if (!this->form()) 58 setForm(findFormAncestor()); 59 if (this->form()) 60 this->form()->registerFormElement(this); 61 } 62 63 inline HTMLObjectElement::~HTMLObjectElement() 64 { 65 if (form()) 66 form()->removeFormElement(this); 67 } 68 69 PassRefPtr<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser) 70 { 71 return adoptRef(new HTMLObjectElement(tagName, document, form, createdByParser)); 72 } 73 74 RenderWidget* HTMLObjectElement::renderWidgetForJSBindings() const 75 { 76 document()->updateLayoutIgnorePendingStylesheets(); 77 return renderPart(); // This will return 0 if the renderer is not a RenderPart. 78 } 79 80 void HTMLObjectElement::parseMappedAttribute(Attribute* attr) 81 { 82 if (attr->name() == typeAttr) { 83 m_serviceType = attr->value().lower(); 84 size_t pos = m_serviceType.find(";"); 85 if (pos != notFound) 86 m_serviceType = m_serviceType.left(pos); 87 if (renderer()) 88 setNeedsWidgetUpdate(true); 89 if (!isImageType() && m_imageLoader) 90 m_imageLoader.clear(); 91 } else if (attr->name() == dataAttr) { 92 m_url = stripLeadingAndTrailingHTMLSpaces(attr->value()); 93 if (renderer()) { 94 setNeedsWidgetUpdate(true); 95 if (isImageType()) { 96 if (!m_imageLoader) 97 m_imageLoader = adoptPtr(new HTMLImageLoader(this)); 98 m_imageLoader->updateFromElementIgnoringPreviousError(); 99 } 100 } 101 } else if (attr->name() == classidAttr) { 102 m_classId = attr->value(); 103 if (renderer()) 104 setNeedsWidgetUpdate(true); 105 } else if (attr->name() == onloadAttr) 106 setAttributeEventListener(eventNames().loadEvent, createAttributeEventListener(this, attr)); 107 else if (attr->name() == onbeforeloadAttr) 108 setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr)); 109 else if (attr->name() == nameAttr) { 110 const AtomicString& newName = attr->value(); 111 if (isDocNamedItem() && inDocument() && document()->isHTMLDocument()) { 112 HTMLDocument* document = static_cast<HTMLDocument*>(this->document()); 113 document->removeNamedItem(m_name); 114 document->addNamedItem(newName); 115 } 116 m_name = newName; 117 } else if (attr->name() == borderAttr) { 118 addCSSLength(attr, CSSPropertyBorderWidth, attr->value().toInt() ? attr->value() : "0"); 119 addCSSProperty(attr, CSSPropertyBorderTopStyle, CSSValueSolid); 120 addCSSProperty(attr, CSSPropertyBorderRightStyle, CSSValueSolid); 121 addCSSProperty(attr, CSSPropertyBorderBottomStyle, CSSValueSolid); 122 addCSSProperty(attr, CSSPropertyBorderLeftStyle, CSSValueSolid); 123 } else if (isIdAttributeName(attr->name())) { 124 const AtomicString& newId = attr->value(); 125 if (isDocNamedItem() && inDocument() && document()->isHTMLDocument()) { 126 HTMLDocument* document = static_cast<HTMLDocument*>(this->document()); 127 document->removeExtraNamedItem(m_id); 128 document->addExtraNamedItem(newId); 129 } 130 m_id = newId; 131 // also call superclass 132 HTMLPlugInImageElement::parseMappedAttribute(attr); 133 } else 134 HTMLPlugInImageElement::parseMappedAttribute(attr); 135 } 136 137 static void mapDataParamToSrc(Vector<String>* paramNames, Vector<String>* paramValues) 138 { 139 // Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP 140 // require "src" attribute). 141 int srcIndex = -1, dataIndex = -1; 142 for (unsigned int i = 0; i < paramNames->size(); ++i) { 143 if (equalIgnoringCase((*paramNames)[i], "src")) 144 srcIndex = i; 145 else if (equalIgnoringCase((*paramNames)[i], "data")) 146 dataIndex = i; 147 } 148 149 if (srcIndex == -1 && dataIndex != -1) { 150 paramNames->append("src"); 151 paramValues->append((*paramValues)[dataIndex]); 152 } 153 } 154 155 // FIXME: This function should not deal with url or serviceType! 156 void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType) 157 { 158 HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames; 159 String urlParameter; 160 161 // Scan the PARAM children and store their name/value pairs. 162 // Get the URL and type from the params if we don't already have them. 163 for (Node* child = firstChild(); child; child = child->nextSibling()) { 164 if (!child->hasTagName(paramTag)) 165 continue; 166 167 HTMLParamElement* p = static_cast<HTMLParamElement*>(child); 168 String name = p->name(); 169 if (name.isEmpty()) 170 continue; 171 172 uniqueParamNames.add(name.impl()); 173 paramNames.append(p->name()); 174 paramValues.append(p->value()); 175 176 // FIXME: url adjustment does not belong in this function. 177 if (url.isEmpty() && urlParameter.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url"))) 178 urlParameter = stripLeadingAndTrailingHTMLSpaces(p->value()); 179 // FIXME: serviceType calculation does not belong in this function. 180 if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) { 181 serviceType = p->value(); 182 size_t pos = serviceType.find(";"); 183 if (pos != notFound) 184 serviceType = serviceType.left(pos); 185 } 186 } 187 188 // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag 189 // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is 190 // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means 191 // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM, 192 // else our Java plugin will misinterpret it. [4004531] 193 String codebase; 194 if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) { 195 codebase = "codebase"; 196 uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already 197 } 198 199 // Turn the attributes of the <object> element into arrays, but don't override <param> values. 200 NamedNodeMap* attributes = this->attributes(true); 201 if (attributes) { 202 for (unsigned i = 0; i < attributes->length(); ++i) { 203 Attribute* it = attributes->attributeItem(i); 204 const AtomicString& name = it->name().localName(); 205 if (!uniqueParamNames.contains(name.impl())) { 206 paramNames.append(name.string()); 207 paramValues.append(it->value().string()); 208 } 209 } 210 } 211 212 mapDataParamToSrc(¶mNames, ¶mValues); 213 214 // HTML5 says that an object resource's URL is specified by the object's data 215 // attribute, not by a param element. However, for compatibility, allow the 216 // resource's URL to be given by a param named "src", "movie", "code" or "url" 217 // if we know that resource points to a plug-in. 218 if (url.isEmpty() && !urlParameter.isEmpty()) { 219 SubframeLoader* loader = document()->frame()->loader()->subframeLoader(); 220 if (loader->resourceWillUsePlugin(urlParameter, serviceType, shouldPreferPlugInsForImages())) 221 url = urlParameter; 222 } 223 } 224 225 226 bool HTMLObjectElement::hasFallbackContent() const 227 { 228 for (Node* child = firstChild(); child; child = child->nextSibling()) { 229 // Ignore whitespace-only text, and <param> tags, any other content is fallback content. 230 if (child->isTextNode()) { 231 if (!static_cast<Text*>(child)->containsOnlyWhitespace()) 232 return true; 233 } else if (!child->hasTagName(paramTag)) 234 return true; 235 } 236 return false; 237 } 238 239 bool HTMLObjectElement::hasValidClassId() 240 { 241 #if PLATFORM(QT) 242 if (equalIgnoringCase(serviceType(), "application/x-qt-plugin") || equalIgnoringCase(serviceType(), "application/x-qt-styled-widget")) 243 return true; 244 #endif 245 246 if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && classId().startsWith("java:", false)) 247 return true; 248 249 // HTML5 says that fallback content should be rendered if a non-empty 250 // classid is specified for which the UA can't find a suitable plug-in. 251 return classId().isEmpty(); 252 } 253 254 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and 255 // moved down into HTMLPluginImageElement.cpp 256 void HTMLObjectElement::updateWidget(PluginCreationOption pluginCreationOption) 257 { 258 ASSERT(!renderEmbeddedObject()->pluginCrashedOrWasMissing()); 259 // FIXME: We should ASSERT(needsWidgetUpdate()), but currently 260 // FrameView::updateWidget() calls updateWidget(false) without checking if 261 // the widget actually needs updating! 262 setNeedsWidgetUpdate(false); 263 // FIXME: This should ASSERT isFinishedParsingChildren() instead. 264 if (!isFinishedParsingChildren()) 265 return; 266 267 String url = this->url(); 268 String serviceType = this->serviceType(); 269 270 // FIXME: These should be joined into a PluginParameters class. 271 Vector<String> paramNames; 272 Vector<String> paramValues; 273 parametersForPlugin(paramNames, paramValues, url, serviceType); 274 275 // Note: url is modified above by parametersForPlugin. 276 if (!allowedToLoadFrameURL(url)) 277 return; 278 279 bool fallbackContent = hasFallbackContent(); 280 renderEmbeddedObject()->setHasFallbackContent(fallbackContent); 281 282 if (pluginCreationOption == CreateOnlyNonNetscapePlugins && wouldLoadAsNetscapePlugin(url, serviceType)) 283 return; 284 285 ASSERT(!m_inBeforeLoadEventHandler); 286 m_inBeforeLoadEventHandler = true; 287 bool beforeLoadAllowedLoad = dispatchBeforeLoadEvent(url); 288 m_inBeforeLoadEventHandler = false; 289 290 // beforeload events can modify the DOM, potentially causing 291 // RenderWidget::destroy() to be called. Ensure we haven't been 292 // destroyed before continuing. 293 // FIXME: Should this render fallback content? 294 if (!renderer()) 295 return; 296 297 SubframeLoader* loader = document()->frame()->loader()->subframeLoader(); 298 bool success = beforeLoadAllowedLoad && hasValidClassId() && loader->requestObject(this, url, getAttribute(nameAttr), serviceType, paramNames, paramValues); 299 300 if (!success && fallbackContent) 301 renderFallbackContent(); 302 } 303 304 bool HTMLObjectElement::rendererIsNeeded(RenderStyle* style) 305 { 306 // FIXME: This check should not be needed, detached documents never render! 307 Frame* frame = document()->frame(); 308 if (!frame) 309 return false; 310 311 return HTMLPlugInImageElement::rendererIsNeeded(style); 312 } 313 314 void HTMLObjectElement::insertedIntoDocument() 315 { 316 HTMLPlugInImageElement::insertedIntoDocument(); 317 if (!inDocument()) 318 return; 319 320 if (isDocNamedItem() && document()->isHTMLDocument()) { 321 HTMLDocument* document = static_cast<HTMLDocument*>(this->document()); 322 document->addNamedItem(m_name); 323 document->addExtraNamedItem(m_id); 324 } 325 326 FormAssociatedElement::insertedIntoDocument(); 327 } 328 329 void HTMLObjectElement::removedFromDocument() 330 { 331 if (isDocNamedItem() && document()->isHTMLDocument()) { 332 HTMLDocument* document = static_cast<HTMLDocument*>(this->document()); 333 document->removeNamedItem(m_name); 334 document->removeExtraNamedItem(m_id); 335 } 336 337 HTMLPlugInImageElement::removedFromDocument(); 338 FormAssociatedElement::removedFromDocument(); 339 } 340 341 void HTMLObjectElement::attributeChanged(Attribute* attr, bool preserveDecls) 342 { 343 if (attr->name() == formAttr) 344 formAttributeChanged(); 345 else 346 HTMLPlugInImageElement::attributeChanged(attr, preserveDecls); 347 } 348 349 void HTMLObjectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) 350 { 351 updateDocNamedItem(); 352 if (inDocument() && !useFallbackContent()) { 353 setNeedsWidgetUpdate(true); 354 setNeedsStyleRecalc(); 355 } 356 HTMLPlugInImageElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); 357 } 358 359 bool HTMLObjectElement::isURLAttribute(Attribute *attr) const 360 { 361 return (attr->name() == dataAttr || (attr->name() == usemapAttr && attr->value().string()[0] != '#')); 362 } 363 364 const QualifiedName& HTMLObjectElement::imageSourceAttributeName() const 365 { 366 return dataAttr; 367 } 368 369 void HTMLObjectElement::renderFallbackContent() 370 { 371 if (useFallbackContent()) 372 return; 373 374 if (!inDocument()) 375 return; 376 377 // Before we give up and use fallback content, check to see if this is a MIME type issue. 378 if (m_imageLoader && m_imageLoader->image() && m_imageLoader->image()->status() != CachedResource::LoadError) { 379 m_serviceType = m_imageLoader->image()->response().mimeType(); 380 if (!isImageType()) { 381 // If we don't think we have an image type anymore, then clear the image from the loader. 382 m_imageLoader->setImage(0); 383 detach(); 384 attach(); 385 return; 386 } 387 } 388 389 m_useFallbackContent = true; 390 391 // FIXME: Style gets recalculated which is suboptimal. 392 detach(); 393 attach(); 394 } 395 396 // FIXME: This should be removed, all callers are almost certainly wrong. 397 static bool isRecognizedTagName(const QualifiedName& tagName) 398 { 399 DEFINE_STATIC_LOCAL(HashSet<AtomicStringImpl*>, tagList, ()); 400 if (tagList.isEmpty()) { 401 size_t tagCount = 0; 402 QualifiedName** tags = HTMLNames::getHTMLTags(&tagCount); 403 for (size_t i = 0; i < tagCount; i++) { 404 if (*tags[i] == bgsoundTag 405 || *tags[i] == commandTag 406 || *tags[i] == detailsTag 407 || *tags[i] == figcaptionTag 408 || *tags[i] == figureTag 409 || *tags[i] == summaryTag 410 || *tags[i] == trackTag) { 411 // Even though we have atoms for these tags, we don't want to 412 // treat them as "recognized tags" for the purpose of parsing 413 // because that changes how we parse documents. 414 continue; 415 } 416 tagList.add(tags[i]->localName().impl()); 417 } 418 } 419 return tagList.contains(tagName.localName().impl()); 420 } 421 422 void HTMLObjectElement::updateDocNamedItem() 423 { 424 // The rule is "<object> elements with no children other than 425 // <param> elements, unknown elements and whitespace can be 426 // found by name in a document, and other <object> elements cannot." 427 bool wasNamedItem = m_docNamedItem; 428 bool isNamedItem = true; 429 Node* child = firstChild(); 430 while (child && isNamedItem) { 431 if (child->isElementNode()) { 432 Element* element = static_cast<Element*>(child); 433 // FIXME: Use of isRecognizedTagName is almost certainly wrong here. 434 if (isRecognizedTagName(element->tagQName()) && !element->hasTagName(paramTag)) 435 isNamedItem = false; 436 } else if (child->isTextNode()) { 437 if (!static_cast<Text*>(child)->containsOnlyWhitespace()) 438 isNamedItem = false; 439 } else 440 isNamedItem = false; 441 child = child->nextSibling(); 442 } 443 if (isNamedItem != wasNamedItem && document()->isHTMLDocument()) { 444 HTMLDocument* document = static_cast<HTMLDocument*>(this->document()); 445 if (isNamedItem) { 446 document->addNamedItem(m_name); 447 document->addExtraNamedItem(m_id); 448 } else { 449 document->removeNamedItem(m_name); 450 document->removeExtraNamedItem(m_id); 451 } 452 } 453 m_docNamedItem = isNamedItem; 454 } 455 456 bool HTMLObjectElement::containsJavaApplet() const 457 { 458 if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr))) 459 return true; 460 461 for (Element* child = firstElementChild(); child; child = child->nextElementSibling()) { 462 if (child->hasTagName(paramTag) 463 && equalIgnoringCase(child->getAttribute(nameAttr), "type") 464 && MIMETypeRegistry::isJavaAppletMIMEType(child->getAttribute(valueAttr).string())) 465 return true; 466 if (child->hasTagName(objectTag) 467 && static_cast<HTMLObjectElement*>(child)->containsJavaApplet()) 468 return true; 469 if (child->hasTagName(appletTag)) 470 return true; 471 } 472 473 return false; 474 } 475 476 void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const 477 { 478 HTMLPlugInImageElement::addSubresourceAttributeURLs(urls); 479 480 addSubresourceURL(urls, document()->completeURL(getAttribute(dataAttr))); 481 482 // FIXME: Passing a string that starts with "#" to the completeURL function does 483 // not seem like it would work. The image element has similar but not identical code. 484 const AtomicString& useMap = getAttribute(usemapAttr); 485 if (useMap.startsWith("#")) 486 addSubresourceURL(urls, document()->completeURL(useMap)); 487 } 488 489 void HTMLObjectElement::willMoveToNewOwnerDocument() 490 { 491 FormAssociatedElement::willMoveToNewOwnerDocument(); 492 HTMLPlugInImageElement::willMoveToNewOwnerDocument(); 493 } 494 495 void HTMLObjectElement::insertedIntoTree(bool deep) 496 { 497 FormAssociatedElement::insertedIntoTree(); 498 HTMLPlugInImageElement::insertedIntoTree(deep); 499 } 500 501 void HTMLObjectElement::removedFromTree(bool deep) 502 { 503 FormAssociatedElement::removedFromTree(); 504 HTMLPlugInImageElement::removedFromTree(deep); 505 } 506 507 bool HTMLObjectElement::appendFormData(FormDataList&, bool) 508 { 509 // FIXME: Implements this function. 510 return false; 511 } 512 513 const AtomicString& HTMLObjectElement::formControlName() const 514 { 515 return m_name.isNull() ? emptyAtom : m_name; 516 } 517 518 HTMLFormElement* HTMLObjectElement::virtualForm() const 519 { 520 return FormAssociatedElement::form(); 521 } 522 523 } 524