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 "bindings/v8/ScriptEventListener.h" 28 #include "core/HTMLNames.h" 29 #include "core/dom/Attribute.h" 30 #include "core/dom/ElementTraversal.h" 31 #include "core/dom/TagCollection.h" 32 #include "core/dom/Text.h" 33 #include "core/dom/shadow/ShadowRoot.h" 34 #include "core/fetch/ImageResource.h" 35 #include "core/html/FormDataList.h" 36 #include "core/html/HTMLDocument.h" 37 #include "core/html/HTMLImageLoader.h" 38 #include "core/html/HTMLMetaElement.h" 39 #include "core/html/HTMLParamElement.h" 40 #include "core/html/parser/HTMLParserIdioms.h" 41 #include "core/frame/Settings.h" 42 #include "core/plugins/PluginView.h" 43 #include "core/rendering/RenderEmbeddedObject.h" 44 #include "platform/MIMETypeRegistry.h" 45 #include "platform/Widget.h" 46 47 namespace WebCore { 48 49 using namespace HTMLNames; 50 51 inline HTMLObjectElement::HTMLObjectElement(Document& document, HTMLFormElement* form, bool createdByParser) 52 : HTMLPlugInElement(objectTag, document, createdByParser, ShouldNotPreferPlugInsForImages) 53 , m_useFallbackContent(false) 54 { 55 ScriptWrappable::init(this); 56 associateByParser(form); 57 } 58 59 inline HTMLObjectElement::~HTMLObjectElement() 60 { 61 #if !ENABLE(OILPAN) 62 setForm(0); 63 #endif 64 } 65 66 PassRefPtrWillBeRawPtr<HTMLObjectElement> HTMLObjectElement::create(Document& document, HTMLFormElement* form, bool createdByParser) 67 { 68 RefPtrWillBeRawPtr<HTMLObjectElement> element = adoptRefWillBeNoop(new HTMLObjectElement(document, form, createdByParser)); 69 element->ensureUserAgentShadowRoot(); 70 return element.release(); 71 } 72 73 void HTMLObjectElement::trace(Visitor* visitor) 74 { 75 FormAssociatedElement::trace(visitor); 76 HTMLPlugInElement::trace(visitor); 77 } 78 79 RenderWidget* HTMLObjectElement::existingRenderWidget() const 80 { 81 return renderPart(); // This will return 0 if the renderer is not a RenderPart. 82 } 83 84 bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const 85 { 86 if (name == borderAttr) 87 return true; 88 return HTMLPlugInElement::isPresentationAttribute(name); 89 } 90 91 void HTMLObjectElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style) 92 { 93 if (name == borderAttr) 94 applyBorderAttributeToStyle(value, style); 95 else 96 HTMLPlugInElement::collectStyleForPresentationAttribute(name, value, style); 97 } 98 99 void HTMLObjectElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 100 { 101 if (name == formAttr) 102 formAttributeChanged(); 103 else if (name == typeAttr) { 104 m_serviceType = value.lower(); 105 size_t pos = m_serviceType.find(";"); 106 if (pos != kNotFound) 107 m_serviceType = m_serviceType.left(pos); 108 // FIXME: What is the right thing to do here? Should we supress the 109 // reload stuff when a persistable widget-type is specified? 110 reloadPluginOnAttributeChange(name); 111 if (!renderer()) 112 requestPluginCreationWithoutRendererIfPossible(); 113 } else if (name == dataAttr) { 114 m_url = stripLeadingAndTrailingHTMLSpaces(value); 115 if (renderer() && isImageType()) { 116 setNeedsWidgetUpdate(true); 117 if (!m_imageLoader) 118 m_imageLoader = HTMLImageLoader::create(this); 119 m_imageLoader->updateFromElementIgnoringPreviousError(); 120 } else { 121 reloadPluginOnAttributeChange(name); 122 } 123 } else if (name == classidAttr) { 124 m_classId = value; 125 reloadPluginOnAttributeChange(name); 126 } else { 127 HTMLPlugInElement::parseAttribute(name, value); 128 } 129 } 130 131 static void mapDataParamToSrc(Vector<String>* paramNames, Vector<String>* paramValues) 132 { 133 // Some plugins don't understand the "data" attribute of the OBJECT tag (i.e. Real and WMP 134 // require "src" attribute). 135 int srcIndex = -1, dataIndex = -1; 136 for (unsigned i = 0; i < paramNames->size(); ++i) { 137 if (equalIgnoringCase((*paramNames)[i], "src")) 138 srcIndex = i; 139 else if (equalIgnoringCase((*paramNames)[i], "data")) 140 dataIndex = i; 141 } 142 143 if (srcIndex == -1 && dataIndex != -1) { 144 paramNames->append("src"); 145 paramValues->append((*paramValues)[dataIndex]); 146 } 147 } 148 149 // FIXME: This function should not deal with url or serviceType! 150 void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType) 151 { 152 HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames; 153 String urlParameter; 154 155 // Scan the PARAM children and store their name/value pairs. 156 // Get the URL and type from the params if we don't already have them. 157 for (HTMLParamElement* p = Traversal<HTMLParamElement>::firstChild(*this); p; p = Traversal<HTMLParamElement>::nextSibling(*p)) { 158 String name = p->name(); 159 if (name.isEmpty()) 160 continue; 161 162 uniqueParamNames.add(name.impl()); 163 paramNames.append(p->name()); 164 paramValues.append(p->value()); 165 166 // FIXME: url adjustment does not belong in this function. 167 if (url.isEmpty() && urlParameter.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url"))) 168 urlParameter = stripLeadingAndTrailingHTMLSpaces(p->value()); 169 // FIXME: serviceType calculation does not belong in this function. 170 if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) { 171 serviceType = p->value(); 172 size_t pos = serviceType.find(";"); 173 if (pos != kNotFound) 174 serviceType = serviceType.left(pos); 175 } 176 } 177 178 // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag 179 // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is 180 // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means 181 // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM, 182 // else our Java plugin will misinterpret it. [4004531] 183 String codebase; 184 if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) { 185 codebase = "codebase"; 186 uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already 187 } 188 189 // Turn the attributes of the <object> element into arrays, but don't override <param> values. 190 if (hasAttributes()) { 191 AttributeCollection attributes = this->attributes(); 192 AttributeCollection::const_iterator end = attributes.end(); 193 for (AttributeCollection::const_iterator it = attributes.begin(); it != end; ++it) { 194 const AtomicString& name = it->name().localName(); 195 if (!uniqueParamNames.contains(name.impl())) { 196 paramNames.append(name.string()); 197 paramValues.append(it->value().string()); 198 } 199 } 200 } 201 202 mapDataParamToSrc(¶mNames, ¶mValues); 203 204 // HTML5 says that an object resource's URL is specified by the object's data 205 // attribute, not by a param element. However, for compatibility, allow the 206 // resource's URL to be given by a param named "src", "movie", "code" or "url" 207 // if we know that resource points to a plug-in. 208 if (url.isEmpty() && !urlParameter.isEmpty()) { 209 KURL completedURL = document().completeURL(urlParameter); 210 bool useFallback; 211 if (shouldUsePlugin(completedURL, serviceType, false, useFallback)) 212 url = urlParameter; 213 } 214 } 215 216 217 bool HTMLObjectElement::hasFallbackContent() const 218 { 219 for (Node* child = firstChild(); child; child = child->nextSibling()) { 220 // Ignore whitespace-only text, and <param> tags, any other content is fallback content. 221 if (child->isTextNode()) { 222 if (!toText(child)->containsOnlyWhitespace()) 223 return true; 224 } else if (!isHTMLParamElement(*child)) { 225 return true; 226 } 227 } 228 return false; 229 } 230 231 bool HTMLObjectElement::shouldAllowQuickTimeClassIdQuirk() 232 { 233 // This site-specific hack maintains compatibility with Mac OS X Wiki Server, 234 // which embeds QuickTime movies using an object tag containing QuickTime's 235 // ActiveX classid. Treat this classid as valid only if OS X Server's unique 236 // 'generator' meta tag is present. Only apply this quirk if there is no 237 // fallback content, which ensures the quirk will disable itself if Wiki 238 // Server is updated to generate an alternate embed tag as fallback content. 239 if (!document().settings() 240 || !document().settings()->needsSiteSpecificQuirks() 241 || hasFallbackContent() 242 || !equalIgnoringCase(classId(), "clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B")) 243 return false; 244 245 RefPtrWillBeRawPtr<TagCollection> metaElements = document().getElementsByTagName(HTMLNames::metaTag.localName()); 246 unsigned length = metaElements->length(); 247 for (unsigned i = 0; i < length; ++i) { 248 ASSERT(metaElements->item(i)->isHTMLElement()); 249 HTMLMetaElement* metaElement = toHTMLMetaElement(metaElements->item(i)); 250 if (equalIgnoringCase(metaElement->name(), "generator") && metaElement->content().startsWith("Mac OS X Server Web Services Server", false)) 251 return true; 252 } 253 254 return false; 255 } 256 257 bool HTMLObjectElement::hasValidClassId() 258 { 259 if (MIMETypeRegistry::isJavaAppletMIMEType(m_serviceType) && classId().startsWith("java:", false)) 260 return true; 261 262 if (shouldAllowQuickTimeClassIdQuirk()) 263 return true; 264 265 // HTML5 says that fallback content should be rendered if a non-empty 266 // classid is specified for which the UA can't find a suitable plug-in. 267 return classId().isEmpty(); 268 } 269 270 void HTMLObjectElement::reloadPluginOnAttributeChange(const QualifiedName& name) 271 { 272 // Following, 273 // http://www.whatwg.org/specs/web-apps/current-work/#the-object-element 274 // (Enumerated list below "Whenever one of the following conditions occur:") 275 // 276 // the updating of certain attributes should bring about "redetermination" 277 // of what the element contains. 278 bool needsInvalidation; 279 if (name == typeAttr) { 280 needsInvalidation = !fastHasAttribute(classidAttr) && !fastHasAttribute(dataAttr); 281 } else if (name == dataAttr) { 282 needsInvalidation = !fastHasAttribute(classidAttr); 283 } else if (name == classidAttr) { 284 needsInvalidation = true; 285 } else { 286 ASSERT_NOT_REACHED(); 287 needsInvalidation = false; 288 } 289 setNeedsWidgetUpdate(true); 290 if (needsInvalidation) 291 setNeedsStyleRecalc(SubtreeStyleChange); 292 } 293 294 // FIXME: This should be unified with HTMLEmbedElement::updateWidget and 295 // moved down into HTMLPluginElement.cpp 296 void HTMLObjectElement::updateWidgetInternal() 297 { 298 ASSERT(!renderEmbeddedObject()->showsUnavailablePluginIndicator()); 299 ASSERT(needsWidgetUpdate()); 300 setNeedsWidgetUpdate(false); 301 // FIXME: This should ASSERT isFinishedParsingChildren() instead. 302 if (!isFinishedParsingChildren()) { 303 dispatchErrorEvent(); 304 return; 305 } 306 307 // FIXME: I'm not sure it's ever possible to get into updateWidget during a 308 // removal, but just in case we should avoid loading the frame to prevent 309 // security bugs. 310 if (!SubframeLoadingDisabler::canLoadFrame(*this)) { 311 dispatchErrorEvent(); 312 return; 313 } 314 315 String url = this->url(); 316 String serviceType = m_serviceType; 317 318 // FIXME: These should be joined into a PluginParameters class. 319 Vector<String> paramNames; 320 Vector<String> paramValues; 321 parametersForPlugin(paramNames, paramValues, url, serviceType); 322 323 // Note: url is modified above by parametersForPlugin. 324 if (!allowedToLoadFrameURL(url)) { 325 dispatchErrorEvent(); 326 return; 327 } 328 329 // FIXME: Is it possible to get here without a renderer now that we don't have beforeload events? 330 if (!renderer()) 331 return; 332 333 if (!hasValidClassId() || !requestObject(url, serviceType, paramNames, paramValues)) { 334 if (!url.isEmpty()) 335 dispatchErrorEvent(); 336 if (hasFallbackContent()) 337 renderFallbackContent(); 338 } 339 } 340 341 bool HTMLObjectElement::rendererIsNeeded(const RenderStyle& style) 342 { 343 // FIXME: This check should not be needed, detached documents never render! 344 if (!document().frame()) 345 return false; 346 return HTMLPlugInElement::rendererIsNeeded(style); 347 } 348 349 Node::InsertionNotificationRequest HTMLObjectElement::insertedInto(ContainerNode* insertionPoint) 350 { 351 HTMLPlugInElement::insertedInto(insertionPoint); 352 FormAssociatedElement::insertedInto(insertionPoint); 353 return InsertionDone; 354 } 355 356 void HTMLObjectElement::removedFrom(ContainerNode* insertionPoint) 357 { 358 HTMLPlugInElement::removedFrom(insertionPoint); 359 FormAssociatedElement::removedFrom(insertionPoint); 360 } 361 362 void HTMLObjectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) 363 { 364 if (inDocument() && !useFallbackContent()) { 365 setNeedsWidgetUpdate(true); 366 setNeedsStyleRecalc(SubtreeStyleChange); 367 } 368 HTMLPlugInElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); 369 } 370 371 bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const 372 { 373 return attribute.name() == codebaseAttr || attribute.name() == dataAttr 374 || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') 375 || HTMLPlugInElement::isURLAttribute(attribute); 376 } 377 378 bool HTMLObjectElement::hasLegalLinkAttribute(const QualifiedName& name) const 379 { 380 return name == classidAttr || name == dataAttr || name == codebaseAttr || HTMLPlugInElement::hasLegalLinkAttribute(name); 381 } 382 383 const QualifiedName& HTMLObjectElement::subResourceAttributeName() const 384 { 385 return dataAttr; 386 } 387 388 const AtomicString HTMLObjectElement::imageSourceURL() const 389 { 390 return getAttribute(dataAttr); 391 } 392 393 // FIXME: Remove this hack. 394 void HTMLObjectElement::reattachFallbackContent() 395 { 396 // This can happen inside of attach() in the middle of a recalcStyle so we need to 397 // reattach synchronously here. 398 if (document().inStyleRecalc()) 399 reattach(); 400 else 401 lazyReattachIfAttached(); 402 } 403 404 void HTMLObjectElement::renderFallbackContent() 405 { 406 if (useFallbackContent()) 407 return; 408 409 if (!inDocument()) 410 return; 411 412 // Before we give up and use fallback content, check to see if this is a MIME type issue. 413 if (m_imageLoader && m_imageLoader->image() && m_imageLoader->image()->status() != Resource::LoadError) { 414 m_serviceType = m_imageLoader->image()->response().mimeType(); 415 if (!isImageType()) { 416 // If we don't think we have an image type anymore, then clear the image from the loader. 417 m_imageLoader->setImage(0); 418 reattachFallbackContent(); 419 return; 420 } 421 } 422 423 m_useFallbackContent = true; 424 425 // FIXME: Style gets recalculated which is suboptimal. 426 reattachFallbackContent(); 427 } 428 429 bool HTMLObjectElement::isExposed() const 430 { 431 // http://www.whatwg.org/specs/web-apps/current-work/#exposed 432 for (HTMLObjectElement* ancestor = Traversal<HTMLObjectElement>::firstAncestor(*this); ancestor; ancestor = Traversal<HTMLObjectElement>::firstAncestor(*ancestor)) { 433 if (ancestor->isExposed()) 434 return false; 435 } 436 for (HTMLElement* element = Traversal<HTMLElement>::firstWithin(*this); element; element = Traversal<HTMLElement>::next(*element, this)) { 437 if (isHTMLObjectElement(*element) || isHTMLEmbedElement(*element)) 438 return false; 439 } 440 return true; 441 } 442 443 bool HTMLObjectElement::containsJavaApplet() const 444 { 445 if (MIMETypeRegistry::isJavaAppletMIMEType(getAttribute(typeAttr))) 446 return true; 447 448 for (HTMLElement* child = Traversal<HTMLElement>::firstChild(*this); child; child = Traversal<HTMLElement>::nextSibling(*child)) { 449 if (isHTMLParamElement(*child) 450 && equalIgnoringCase(child->getNameAttribute(), "type") 451 && MIMETypeRegistry::isJavaAppletMIMEType(child->getAttribute(valueAttr).string())) 452 return true; 453 if (isHTMLObjectElement(*child) && toHTMLObjectElement(*child).containsJavaApplet()) 454 return true; 455 if (isHTMLAppletElement(*child)) 456 return true; 457 } 458 459 return false; 460 } 461 462 void HTMLObjectElement::didMoveToNewDocument(Document& oldDocument) 463 { 464 FormAssociatedElement::didMoveToNewDocument(oldDocument); 465 HTMLPlugInElement::didMoveToNewDocument(oldDocument); 466 } 467 468 bool HTMLObjectElement::appendFormData(FormDataList& encoding, bool) 469 { 470 if (name().isEmpty()) 471 return false; 472 473 Widget* widget = pluginWidget(); 474 if (!widget || !widget->isPluginView()) 475 return false; 476 String value; 477 if (!toPluginView(widget)->getFormValue(value)) 478 return false; 479 encoding.appendData(name(), value); 480 return true; 481 } 482 483 HTMLFormElement* HTMLObjectElement::formOwner() const 484 { 485 return FormAssociatedElement::form(); 486 } 487 488 bool HTMLObjectElement::isInteractiveContent() const 489 { 490 return fastHasAttribute(usemapAttr); 491 } 492 493 bool HTMLObjectElement::useFallbackContent() const 494 { 495 return HTMLPlugInElement::useFallbackContent() || m_useFallbackContent; 496 } 497 498 } 499