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 "core/html/HTMLObjectElement.h" 26 27 #include "HTMLNames.h" 28 #include "bindings/v8/ScriptEventListener.h" 29 #include "core/dom/Attribute.h" 30 #include "core/dom/EventNames.h" 31 #include "core/dom/NodeList.h" 32 #include "core/dom/NodeTraversal.h" 33 #include "core/dom/Text.h" 34 #include "core/html/FormDataList.h" 35 #include "core/html/HTMLDocument.h" 36 #include "core/html/HTMLImageLoader.h" 37 #include "core/html/HTMLMetaElement.h" 38 #include "core/html/HTMLParamElement.h" 39 #include "core/html/parser/HTMLParserIdioms.h" 40 #include "core/loader/cache/ImageResource.h" 41 #include "core/page/Frame.h" 42 #include "core/page/Page.h" 43 #include "core/page/Settings.h" 44 #include "core/platform/MIMETypeRegistry.h" 45 #include "core/platform/Widget.h" 46 #include "core/plugins/PluginView.h" 47 #include "core/rendering/RenderEmbeddedObject.h" 48 49 namespace WebCore { 50 51 using namespace HTMLNames; 52 53 inline HTMLObjectElement::HTMLObjectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser) 54 : HTMLPlugInImageElement(tagName, document, createdByParser, ShouldNotPreferPlugInsForImages) 55 , m_docNamedItem(true) 56 , m_useFallbackContent(false) 57 { 58 ASSERT(hasTagName(objectTag)); 59 setForm(form ? form : findFormAncestor()); 60 ScriptWrappable::init(this); 61 } 62 63 inline HTMLObjectElement::~HTMLObjectElement() 64 { 65 } 66 67 PassRefPtr<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form, bool createdByParser) 68 { 69 return adoptRef(new HTMLObjectElement(tagName, document, form, createdByParser)); 70 } 71 72 RenderWidget* HTMLObjectElement::renderWidgetForJSBindings() const 73 { 74 document()->updateLayoutIgnorePendingStylesheets(); 75 return renderPart(); // This will return 0 if the renderer is not a RenderPart. 76 } 77 78 bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const 79 { 80 if (name == borderAttr) 81 return true; 82 return HTMLPlugInImageElement::isPresentationAttribute(name); 83 } 84 85 void HTMLObjectElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style) 86 { 87 if (name == borderAttr) 88 applyBorderAttributeToStyle(value, style); 89 else 90 HTMLPlugInImageElement::collectStyleForPresentationAttribute(name, value, style); 91 } 92 93 void HTMLObjectElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 94 { 95 if (name == formAttr) 96 formAttributeChanged(); 97 else if (name == typeAttr) { 98 m_serviceType = value.lower(); 99 size_t pos = m_serviceType.find(";"); 100 if (pos != notFound) 101 m_serviceType = m_serviceType.left(pos); 102 if (renderer()) 103 setNeedsWidgetUpdate(true); 104 } else if (name == dataAttr) { 105 m_url = stripLeadingAndTrailingHTMLSpaces(value); 106 if (renderer()) { 107 setNeedsWidgetUpdate(true); 108 if (isImageType()) { 109 if (!m_imageLoader) 110 m_imageLoader = adoptPtr(new HTMLImageLoader(this)); 111 m_imageLoader->updateFromElementIgnoringPreviousError(); 112 } 113 } 114 } else if (name == classidAttr) { 115 m_classId = value; 116 if (renderer()) 117 setNeedsWidgetUpdate(true); 118 } else if (name == onbeforeloadAttr) 119 setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, name, value)); 120 else 121 HTMLPlugInImageElement::parseAttribute(name, value); 122 } 123 124 static void mapDataParamToSrc(Vector<String>* paramNames, Vector<String>* paramValues) 125 { 126 // Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP 127 // require "src" attribute). 128 int srcIndex = -1, dataIndex = -1; 129 for (unsigned int i = 0; i < paramNames->size(); ++i) { 130 if (equalIgnoringCase((*paramNames)[i], "src")) 131 srcIndex = i; 132 else if (equalIgnoringCase((*paramNames)[i], "data")) 133 dataIndex = i; 134 } 135 136 if (srcIndex == -1 && dataIndex != -1) { 137 paramNames->append("src"); 138 paramValues->append((*paramValues)[dataIndex]); 139 } 140 } 141 142 // FIXME: This function should not deal with url or serviceType! 143 void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType) 144 { 145 HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames; 146 String urlParameter; 147 148 // Scan the PARAM children and store their name/value pairs. 149 // Get the URL and type from the params if we don't already have them. 150 for (Node* child = firstChild(); child; child = child->nextSibling()) { 151 if (!child->hasTagName(paramTag)) 152 continue; 153 154 HTMLParamElement* p = static_cast<HTMLParamElement*>(child); 155 String name = p->name(); 156 if (name.isEmpty()) 157 continue; 158 159 uniqueParamNames.add(name.impl()); 160 paramNames.append(p->name()); 161 paramValues.append(p->value()); 162 163 // FIXME: url adjustment does not belong in this function. 164 if (url.isEmpty() && urlParameter.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url"))) 165 urlParameter = stripLeadingAndTrailingHTMLSpaces(p->value()); 166 // FIXME: serviceType calculation does not belong in this function. 167 if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) { 168 serviceType = p->value(); 169 size_t pos = serviceType.find(";"); 170 if (pos != notFound) 171 serviceType = serviceType.left(pos); 172 } 173 } 174 175 // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag 176 // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is 177 // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means 178 // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM, 179 // else our Java plugin will misinterpret it. [4004531] 180 String codebase; 181 if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) { 182 codebase = "codebase"; 183 uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already 184 } 185 186 // Turn the attributes of the <object> element into arrays, but don't override <param> values. 187 if (hasAttributes()) { 188 for (unsigned i = 0; i < attributeCount(); ++i) { 189 const Attribute* attribute = attributeItem(i); 190 const AtomicString& name = attribute->name().localName(); 191 if (!uniqueParamNames.contains(name.impl())) { 192 paramNames.append(name.string()); 193 paramValues.append(attribute->value().string()); 194 } 195 } 196 } 197 198 mapDataParamToSrc(¶mNames, ¶mValues); 199 200 // HTML5 says that an object resource's URL is specified by the object's data 201 // attribute, not by a param element. However, for compatibility, allow the 202 // resource's URL to be given by a param named "src", "movie", "code" or "url" 203 // if we know that resource points to a plug-in. 204 if (url.isEmpty() && !urlParameter.isEmpty()) { 205 KURL completedURL = document()->completeURL(urlParameter); 206 bool useFallback; 207 if (shouldUsePlugin(completedURL, serviceType, false, useFallback)) 208 url = urlParameter; 209 } 210 } 211 212 213 bool HTMLObjectElement::hasFallbackContent() const 214 { 215 for (Node* child = firstChild(); child; child = child->nextSibling()) { 216 // Ignore whitespace-only text, and <param> tags, any other content is fallback content. 217 if (child->isTextNode()) { 218 if (!toText(child)->containsOnlyWhitespace()) 219 return true; 220 } else if (!child->hasTagName(paramTag)) 221 return true; 222 } 223 return false; 224 } 225 226 bool HTMLObjectElement::shouldAllowQuickTimeClassIdQuirk() 227 { 228 // This site-specific hack maintains compatibility with Mac OS X Wiki Server, 229 // which embeds QuickTime movies using an object tag containing QuickTime's 230 // ActiveX classid. Treat this classid as valid only if OS X Server's unique 231 // 'generator' meta tag is present. Only apply this quirk if there is no 232 // fallback content, which ensures the quirk will disable itself if Wiki 233 // Server is updated to generate an alternate embed tag as fallback content. 234 if (!document()->page() 235 || !document()->page()->settings()->needsSiteSpecificQuirks() 236 || hasFallbackContent() 237 || !equalIgnoringCase(classId(), "clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B")) 238 return false; 239 240 RefPtr<NodeList> metaElements = document()->getElementsByTagName(HTMLNames::metaTag.localName()); 241 unsigned length = metaElements->length(); 242 for (unsigned i = 0; i < length; ++i) { 243 ASSERT(metaElements->item(i)->isHTMLElement()); 244 HTMLMetaElement* metaElement = static_cast<HTMLMetaElement*>(metaElements->item(i)); 245 if (equalIgnoringCase(metaElement->name(), "generator") && metaElement->content().startsWith("Mac OS X Server Web Services Server", false)) 246 return true; 247 } 248 249 return false; 250 } 251 252 bool HTMLObjectElement::hasValidClassId() 253 { 254 if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && classId().startsWith("java:", false)) 255 return true; 256 257 if (shouldAllowQuickTimeClassIdQuirk()) 258 return true; 259 260 // HTML5 says that fallback content should be rendered if a non-empty 261 // classid is specified for which the UA can't find a suitable plug-in. 262 return classId().isEmpty(); 263 } 264 265 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and 266 // moved down into HTMLPluginImageElement.cpp 267 void HTMLObjectElement::updateWidget(PluginCreationOption pluginCreationOption) 268 { 269 ASSERT(!renderEmbeddedObject()->showsUnavailablePluginIndicator()); 270 ASSERT(needsWidgetUpdate()); 271 setNeedsWidgetUpdate(false); 272 // FIXME: This should ASSERT isFinishedParsingChildren() instead. 273 if (!isFinishedParsingChildren()) 274 return; 275 276 // FIXME: I'm not sure it's ever possible to get into updateWidget during a 277 // removal, but just in case we should avoid loading the frame to prevent 278 // security bugs. 279 if (!SubframeLoadingDisabler::canLoadFrame(this)) 280 return; 281 282 String url = this->url(); 283 String serviceType = this->serviceType(); 284 285 // FIXME: These should be joined into a PluginParameters class. 286 Vector<String> paramNames; 287 Vector<String> paramValues; 288 parametersForPlugin(paramNames, paramValues, url, serviceType); 289 290 // Note: url is modified above by parametersForPlugin. 291 if (!allowedToLoadFrameURL(url)) 292 return; 293 294 bool fallbackContent = hasFallbackContent(); 295 renderEmbeddedObject()->setHasFallbackContent(fallbackContent); 296 297 // FIXME: It's sadness that we have this special case here. 298 // See http://trac.webkit.org/changeset/25128 and 299 // plugins/netscape-plugin-setwindow-size.html 300 if (pluginCreationOption == CreateOnlyNonNetscapePlugins && wouldLoadAsNetscapePlugin(url, serviceType)) { 301 // Ensure updateWidget() is called again during layout to create the Netscape plug-in. 302 setNeedsWidgetUpdate(true); 303 return; 304 } 305 306 RefPtr<HTMLObjectElement> protect(this); // beforeload and plugin loading can make arbitrary DOM mutations. 307 bool beforeLoadAllowedLoad = dispatchBeforeLoadEvent(url); 308 if (!renderer()) // Do not load the plugin if beforeload removed this element or its renderer. 309 return; 310 311 bool success = beforeLoadAllowedLoad && hasValidClassId() && requestObject(url, serviceType, paramNames, paramValues); 312 if (!success && fallbackContent) 313 renderFallbackContent(); 314 } 315 316 bool HTMLObjectElement::rendererIsNeeded(const NodeRenderingContext& context) 317 { 318 // FIXME: This check should not be needed, detached documents never render! 319 Frame* frame = document()->frame(); 320 if (!frame) 321 return false; 322 323 return HTMLPlugInImageElement::rendererIsNeeded(context); 324 } 325 326 Node::InsertionNotificationRequest HTMLObjectElement::insertedInto(ContainerNode* insertionPoint) 327 { 328 HTMLPlugInImageElement::insertedInto(insertionPoint); 329 FormAssociatedElement::insertedInto(insertionPoint); 330 return InsertionDone; 331 } 332 333 void HTMLObjectElement::removedFrom(ContainerNode* insertionPoint) 334 { 335 HTMLPlugInImageElement::removedFrom(insertionPoint); 336 FormAssociatedElement::removedFrom(insertionPoint); 337 } 338 339 void HTMLObjectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) 340 { 341 updateDocNamedItem(); 342 if (inDocument() && !useFallbackContent()) { 343 setNeedsWidgetUpdate(true); 344 setNeedsStyleRecalc(); 345 } 346 HTMLPlugInImageElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); 347 } 348 349 bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const 350 { 351 return attribute.name() == dataAttr || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') || HTMLPlugInImageElement::isURLAttribute(attribute); 352 } 353 354 const AtomicString& HTMLObjectElement::imageSourceURL() const 355 { 356 return getAttribute(dataAttr); 357 } 358 359 // FIXME: Remove this hack. 360 void HTMLObjectElement::reattachFallbackContent() 361 { 362 // This can happen inside of attach() in the middle of a recalcStyle so we need to 363 // reattach synchronously here. 364 if (document()->inStyleRecalc()) 365 reattach(); 366 else 367 lazyReattach(); 368 } 369 370 void HTMLObjectElement::renderFallbackContent() 371 { 372 if (useFallbackContent()) 373 return; 374 375 if (!inDocument()) 376 return; 377 378 // Before we give up and use fallback content, check to see if this is a MIME type issue. 379 if (m_imageLoader && m_imageLoader->image() && m_imageLoader->image()->status() != Resource::LoadError) { 380 m_serviceType = m_imageLoader->image()->response().mimeType(); 381 if (!isImageType()) { 382 // If we don't think we have an image type anymore, then clear the image from the loader. 383 m_imageLoader->setImage(0); 384 reattachFallbackContent(); 385 return; 386 } 387 } 388 389 m_useFallbackContent = true; 390 391 // FIXME: Style gets recalculated which is suboptimal. 392 reattachFallbackContent(); 393 } 394 395 // FIXME: This should be removed, all callers are almost certainly wrong. 396 static bool isRecognizedTagName(const QualifiedName& tagName) 397 { 398 DEFINE_STATIC_LOCAL(HashSet<StringImpl*>, tagList, ()); 399 if (tagList.isEmpty()) { 400 QualifiedName** tags = HTMLNames::getHTMLTags(); 401 for (size_t i = 0; i < HTMLNames::HTMLTagsCount; i++) { 402 if (*tags[i] == bgsoundTag 403 || *tags[i] == commandTag 404 || *tags[i] == detailsTag 405 || *tags[i] == figcaptionTag 406 || *tags[i] == figureTag 407 || *tags[i] == summaryTag 408 || *tags[i] == trackTag) { 409 // Even though we have atoms for these tags, we don't want to 410 // treat them as "recognized tags" for the purpose of parsing 411 // because that changes how we parse documents. 412 continue; 413 } 414 tagList.add(tags[i]->localName().impl()); 415 } 416 } 417 return tagList.contains(tagName.localName().impl()); 418 } 419 420 void HTMLObjectElement::updateDocNamedItem() 421 { 422 // The rule is "<object> elements with no children other than 423 // <param> elements, unknown elements and whitespace can be 424 // found by name in a document, and other <object> elements cannot." 425 bool wasNamedItem = m_docNamedItem; 426 bool isNamedItem = true; 427 Node* child = firstChild(); 428 while (child && isNamedItem) { 429 if (child->isElementNode()) { 430 Element* element = toElement(child); 431 // FIXME: Use of isRecognizedTagName is almost certainly wrong here. 432 if (isRecognizedTagName(element->tagQName()) && !element->hasTagName(paramTag)) 433 isNamedItem = false; 434 } else if (child->isTextNode()) { 435 if (!toText(child)->containsOnlyWhitespace()) 436 isNamedItem = false; 437 } else 438 isNamedItem = false; 439 child = child->nextSibling(); 440 } 441 if (isNamedItem != wasNamedItem && document()->isHTMLDocument()) { 442 HTMLDocument* document = toHTMLDocument(this->document()); 443 if (isNamedItem) { 444 document->addNamedItem(getNameAttribute()); 445 document->addExtraNamedItem(getIdAttribute()); 446 } else { 447 document->removeNamedItem(getNameAttribute()); 448 document->removeExtraNamedItem(getIdAttribute()); 449 } 450 } 451 m_docNamedItem = isNamedItem; 452 } 453 454 bool HTMLObjectElement::containsJavaApplet() const 455 { 456 if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr))) 457 return true; 458 459 for (Element* child = ElementTraversal::firstWithin(this); child; child = ElementTraversal::nextSkippingChildren(child, this)) { 460 if (child->hasTagName(paramTag) 461 && equalIgnoringCase(child->getNameAttribute(), "type") 462 && MIMETypeRegistry::isJavaAppletMIMEType(child->getAttribute(valueAttr).string())) 463 return true; 464 if (child->hasTagName(objectTag) 465 && static_cast<HTMLObjectElement*>(child)->containsJavaApplet()) 466 return true; 467 if (child->hasTagName(appletTag)) 468 return true; 469 } 470 471 return false; 472 } 473 474 void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const 475 { 476 HTMLPlugInImageElement::addSubresourceAttributeURLs(urls); 477 478 addSubresourceURL(urls, document()->completeURL(getAttribute(dataAttr))); 479 480 // FIXME: Passing a string that starts with "#" to the completeURL function does 481 // not seem like it would work. The image element has similar but not identical code. 482 const AtomicString& useMap = getAttribute(usemapAttr); 483 if (useMap.startsWith('#')) 484 addSubresourceURL(urls, document()->completeURL(useMap)); 485 } 486 487 void HTMLObjectElement::didMoveToNewDocument(Document* oldDocument) 488 { 489 FormAssociatedElement::didMoveToNewDocument(oldDocument); 490 HTMLPlugInImageElement::didMoveToNewDocument(oldDocument); 491 } 492 493 bool HTMLObjectElement::appendFormData(FormDataList& encoding, bool) 494 { 495 if (name().isEmpty()) 496 return false; 497 498 Widget* widget = pluginWidget(); 499 if (!widget || !widget->isPluginView()) 500 return false; 501 String value; 502 if (!toPluginView(widget)->getFormValue(value)) 503 return false; 504 encoding.appendData(name(), value); 505 return true; 506 } 507 508 HTMLFormElement* HTMLObjectElement::virtualForm() const 509 { 510 return FormAssociatedElement::form(); 511 } 512 513 } 514