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