• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&paramNames, &paramValues);
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