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 "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
HTMLObjectElement(const QualifiedName & tagName,Document * document,HTMLFormElement * form,bool createdByParser)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
~HTMLObjectElement()63 inline HTMLObjectElement::~HTMLObjectElement()
64 {
65 if (form())
66 form()->removeFormElement(this);
67 }
68
create(const QualifiedName & tagName,Document * document,HTMLFormElement * form,bool createdByParser)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
renderWidgetForJSBindings() const74 RenderWidget* HTMLObjectElement::renderWidgetForJSBindings() const
75 {
76 document()->updateLayoutIgnorePendingStylesheets();
77 return renderPart(); // This will return 0 if the renderer is not a RenderPart.
78 }
79
parseMappedAttribute(Attribute * attr)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
mapDataParamToSrc(Vector<String> * paramNames,Vector<String> * paramValues)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!
parametersForPlugin(Vector<String> & paramNames,Vector<String> & paramValues,String & url,String & 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
hasFallbackContent() const226 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
hasValidClassId()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
updateWidget(PluginCreationOption pluginCreationOption)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
rendererIsNeeded(RenderStyle * style)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
insertedIntoDocument()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
removedFromDocument()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
attributeChanged(Attribute * attr,bool preserveDecls)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
childrenChanged(bool changedByParser,Node * beforeChange,Node * afterChange,int childCountDelta)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
isURLAttribute(Attribute * attr) const359 bool HTMLObjectElement::isURLAttribute(Attribute *attr) const
360 {
361 return (attr->name() == dataAttr || (attr->name() == usemapAttr && attr->value().string()[0] != '#'));
362 }
363
imageSourceAttributeName() const364 const QualifiedName& HTMLObjectElement::imageSourceAttributeName() const
365 {
366 return dataAttr;
367 }
368
renderFallbackContent()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.
isRecognizedTagName(const QualifiedName & tagName)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
updateDocNamedItem()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
containsJavaApplet() const456 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
addSubresourceAttributeURLs(ListHashSet<KURL> & urls) const476 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
willMoveToNewOwnerDocument()489 void HTMLObjectElement::willMoveToNewOwnerDocument()
490 {
491 FormAssociatedElement::willMoveToNewOwnerDocument();
492 HTMLPlugInImageElement::willMoveToNewOwnerDocument();
493 }
494
insertedIntoTree(bool deep)495 void HTMLObjectElement::insertedIntoTree(bool deep)
496 {
497 FormAssociatedElement::insertedIntoTree();
498 HTMLPlugInImageElement::insertedIntoTree(deep);
499 }
500
removedFromTree(bool deep)501 void HTMLObjectElement::removedFromTree(bool deep)
502 {
503 FormAssociatedElement::removedFromTree();
504 HTMLPlugInImageElement::removedFromTree(deep);
505 }
506
appendFormData(FormDataList &,bool)507 bool HTMLObjectElement::appendFormData(FormDataList&, bool)
508 {
509 // FIXME: Implements this function.
510 return false;
511 }
512
formControlName() const513 const AtomicString& HTMLObjectElement::formControlName() const
514 {
515 return m_name.isNull() ? emptyAtom : m_name;
516 }
517
virtualForm() const518 HTMLFormElement* HTMLObjectElement::virtualForm() const
519 {
520 return FormAssociatedElement::form();
521 }
522
523 }
524