1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 2000 Simon Hausmann <hausmann@kde.org>
4 *           (C) 2000 Stefan Schimanski (1Stein@gmx.de)
5 * Copyright (C) 2004, 2005, 2006, 2008, 2009, 2010 Apple Inc. All rights reserved.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB.  If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 *
22 */
23
24#include "config.h"
25#include "RenderEmbeddedObject.h"
26
27#include "Frame.h"
28#include "FrameLoaderClient.h"
29#include "HTMLEmbedElement.h"
30#include "HTMLIFrameElement.h"
31#include "HTMLNames.h"
32#include "HTMLObjectElement.h"
33#include "HTMLParamElement.h"
34#include "MIMETypeRegistry.h"
35#include "Page.h"
36#include "PluginWidget.h"
37#include "RenderView.h"
38#include "RenderWidgetProtector.h"
39#include "Text.h"
40
41#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
42#include "HTMLVideoElement.h"
43#endif
44
45#if USE(ACCELERATED_COMPOSITING)
46#include "PluginWidget.h"
47#endif
48
49namespace WebCore {
50
51using namespace HTMLNames;
52
53RenderEmbeddedObject::RenderEmbeddedObject(Element* element)
54    : RenderPartObject(element)
55{
56    view()->frameView()->setIsVisuallyNonEmpty();
57}
58
59RenderEmbeddedObject::~RenderEmbeddedObject()
60{
61    if (frameView())
62        frameView()->removeWidgetToUpdate(this);
63}
64
65#if USE(ACCELERATED_COMPOSITING)
66bool RenderEmbeddedObject::requiresLayer() const
67{
68    if (RenderPartObject::requiresLayer())
69        return true;
70
71    return allowsAcceleratedCompositing();
72}
73
74bool RenderEmbeddedObject::allowsAcceleratedCompositing() const
75{
76    return widget() && widget()->isPluginWidget() && static_cast<PluginWidget*>(widget())->platformLayer();
77}
78#endif
79
80static bool isURLAllowed(Document* doc, const String& url)
81{
82    if (doc->frame()->page()->frameCount() >= 200)
83        return false;
84
85    // We allow one level of self-reference because some sites depend on that.
86    // But we don't allow more than one.
87    KURL completeURL = doc->completeURL(url);
88    bool foundSelfReference = false;
89    for (Frame* frame = doc->frame(); frame; frame = frame->tree()->parent()) {
90        if (equalIgnoringFragmentIdentifier(frame->loader()->url(), completeURL)) {
91            if (foundSelfReference)
92                return false;
93            foundSelfReference = true;
94        }
95    }
96    return true;
97}
98
99typedef HashMap<String, String, CaseFoldingHash> ClassIdToTypeMap;
100
101static ClassIdToTypeMap* createClassIdToTypeMap()
102{
103    ClassIdToTypeMap* map = new ClassIdToTypeMap;
104    map->add("clsid:D27CDB6E-AE6D-11CF-96B8-444553540000", "application/x-shockwave-flash");
105    map->add("clsid:CFCDAA03-8BE4-11CF-B84B-0020AFBBCCFA", "audio/x-pn-realaudio-plugin");
106    map->add("clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B", "video/quicktime");
107    map->add("clsid:166B1BCA-3F9C-11CF-8075-444553540000", "application/x-director");
108    map->add("clsid:6BF52A52-394A-11D3-B153-00C04F79FAA6", "application/x-mplayer2");
109    map->add("clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95", "application/x-mplayer2");
110    return map;
111}
112
113static String serviceTypeForClassId(const String& classId)
114{
115    // Return early if classId is empty (since we won't do anything below).
116    // Furthermore, if classId is null, calling get() below will crash.
117    if (classId.isEmpty())
118        return String();
119
120    static ClassIdToTypeMap* map = createClassIdToTypeMap();
121    return map->get(classId);
122}
123
124static 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
142void RenderEmbeddedObject::updateWidget(bool onlyCreateNonNetscapePlugins)
143{
144    String url;
145    String serviceType;
146    Vector<String> paramNames;
147    Vector<String> paramValues;
148    Frame* frame = frameView()->frame();
149
150    // The calls to FrameLoader::requestObject within this function can result in a plug-in being initialized.
151    // This can run cause arbitrary JavaScript to run and may result in this RenderObject being detached from
152    // the render tree and destroyed, causing a crash like <rdar://problem/6954546>.  By extending our lifetime
153    // artifically to ensure that we remain alive for the duration of plug-in initialization.
154    RenderWidgetProtector protector(this);
155
156    if (node()->hasTagName(objectTag)) {
157        HTMLObjectElement* objectElement = static_cast<HTMLObjectElement*>(node());
158
159        objectElement->setNeedWidgetUpdate(false);
160        if (!objectElement->isFinishedParsingChildren())
161          return;
162
163        // Check for a child EMBED tag.
164        HTMLEmbedElement* embed = 0;
165        for (Node* child = objectElement->firstChild(); child; ) {
166            if (child->hasTagName(embedTag)) {
167                embed = static_cast<HTMLEmbedElement*>(child);
168                break;
169            }
170
171            if (child->hasTagName(objectTag))
172                child = child->nextSibling(); // Don't descend into nested OBJECT tags
173            else
174                child = child->traverseNextNode(objectElement); // Otherwise descend (EMBEDs may be inside COMMENT tags)
175        }
176
177        // Use the attributes from the EMBED tag instead of the OBJECT tag including WIDTH and HEIGHT.
178        HTMLElement* embedOrObject;
179        if (embed) {
180            embedOrObject = embed;
181            url = embed->url();
182            serviceType = embed->serviceType();
183        } else
184            embedOrObject = objectElement;
185
186        // If there was no URL or type defined in EMBED, try the OBJECT tag.
187        if (url.isEmpty())
188            url = objectElement->url();
189        if (serviceType.isEmpty())
190            serviceType = objectElement->serviceType();
191
192        HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames;
193
194        // Scan the PARAM children.
195        // Get the URL and type from the params if we don't already have them.
196        // Get the attributes from the params if there is no EMBED tag.
197        Node* child = objectElement->firstChild();
198        while (child && (url.isEmpty() || serviceType.isEmpty() || !embed)) {
199            if (child->hasTagName(paramTag)) {
200                HTMLParamElement* p = static_cast<HTMLParamElement*>(child);
201                String name = p->name();
202                if (url.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url")))
203                    url = p->value();
204                if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) {
205                    serviceType = p->value();
206                    int pos = serviceType.find(";");
207                    if (pos != -1)
208                        serviceType = serviceType.left(pos);
209                }
210                if (!embed && !name.isEmpty()) {
211                    uniqueParamNames.add(name.impl());
212                    paramNames.append(p->name());
213                    paramValues.append(p->value());
214                }
215            }
216            child = child->nextSibling();
217        }
218
219        // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag
220        // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is
221        // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means
222        // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM,
223        // else our Java plugin will misinterpret it. [4004531]
224        String codebase;
225        if (!embed && MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
226            codebase = "codebase";
227            uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already
228        }
229
230        // Turn the attributes of either the EMBED tag or OBJECT tag into arrays, but don't override PARAM values.
231        NamedNodeMap* attributes = embedOrObject->attributes();
232        if (attributes) {
233            for (unsigned i = 0; i < attributes->length(); ++i) {
234                Attribute* it = attributes->attributeItem(i);
235                const AtomicString& name = it->name().localName();
236                if (embed || !uniqueParamNames.contains(name.impl())) {
237                    paramNames.append(name.string());
238                    paramValues.append(it->value().string());
239                }
240            }
241        }
242
243        mapDataParamToSrc(&paramNames, &paramValues);
244
245        // If we still don't have a type, try to map from a specific CLASSID to a type.
246        if (serviceType.isEmpty())
247            serviceType = serviceTypeForClassId(objectElement->classId());
248
249        if (!isURLAllowed(document(), url))
250            return;
251
252        // Find out if we support fallback content.
253        m_hasFallbackContent = false;
254        for (Node* child = objectElement->firstChild(); child && !m_hasFallbackContent; child = child->nextSibling()) {
255            if ((!child->isTextNode() && !child->hasTagName(embedTag) && !child->hasTagName(paramTag))  // Discount <embed> and <param>
256                || (child->isTextNode() && !static_cast<Text*>(child)->containsOnlyWhitespace()))
257                m_hasFallbackContent = true;
258        }
259
260        if (onlyCreateNonNetscapePlugins) {
261            KURL completedURL;
262            if (!url.isEmpty())
263                completedURL = frame->loader()->completeURL(url);
264
265            if (frame->loader()->client()->objectContentType(completedURL, serviceType) == ObjectContentNetscapePlugin)
266                return;
267        }
268
269        bool success = objectElement->dispatchBeforeLoadEvent(url) && frame->loader()->requestObject(this, url, objectElement->getAttribute(nameAttr), serviceType, paramNames, paramValues);
270        if (!success && m_hasFallbackContent)
271            objectElement->renderFallbackContent();
272
273    } else if (node()->hasTagName(embedTag)) {
274        HTMLEmbedElement* embedElement = static_cast<HTMLEmbedElement*>(node());
275        embedElement->setNeedWidgetUpdate(false);
276        url = embedElement->url();
277        serviceType = embedElement->serviceType();
278
279        if (url.isEmpty() && serviceType.isEmpty())
280            return;
281        if (!isURLAllowed(document(), url))
282            return;
283
284        // add all attributes set on the embed object
285        NamedNodeMap* attributes = embedElement->attributes();
286        if (attributes) {
287            for (unsigned i = 0; i < attributes->length(); ++i) {
288                Attribute* it = attributes->attributeItem(i);
289                paramNames.append(it->name().localName().string());
290                paramValues.append(it->value().string());
291            }
292        }
293
294        if (onlyCreateNonNetscapePlugins) {
295            KURL completedURL;
296            if (!url.isEmpty())
297                completedURL = frame->loader()->completeURL(url);
298
299            if (frame->loader()->client()->objectContentType(completedURL, serviceType) == ObjectContentNetscapePlugin)
300                return;
301        }
302
303        if (embedElement->dispatchBeforeLoadEvent(url))
304            frame->loader()->requestObject(this, url, embedElement->getAttribute(nameAttr), serviceType, paramNames, paramValues);
305    }
306#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
307    else if (node()->hasTagName(videoTag) || node()->hasTagName(audioTag)) {
308        HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(node());
309
310        mediaElement->setNeedWidgetUpdate(false);
311        if (node()->hasTagName(videoTag)) {
312            HTMLVideoElement* vid = static_cast<HTMLVideoElement*>(node());
313            String poster = vid->poster();
314            if (!poster.isEmpty()) {
315                paramNames.append("_media_element_poster_");
316                paramValues.append(poster);
317            }
318        }
319
320        url = mediaElement->initialURL();
321        if (!url.isEmpty()) {
322            paramNames.append("_media_element_src_");
323            paramValues.append(url);
324        }
325
326        serviceType = "application/x-media-element-proxy-plugin";
327
328        if (mediaElement->dispatchBeforeLoadEvent(url))
329            frame->loader()->requestObject(this, url, nullAtom, serviceType, paramNames, paramValues);
330    }
331#endif
332}
333
334void RenderEmbeddedObject::layout()
335{
336    ASSERT(needsLayout());
337
338    calcWidth();
339    calcHeight();
340
341    RenderPart::layout();
342
343    m_overflow.clear();
344    addShadowOverflow();
345
346    if (!widget() && frameView())
347        frameView()->addWidgetToUpdate(this);
348
349    setNeedsLayout(false);
350}
351
352}
353