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-2017 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 "DOMFormData.h"
31#include "ElementIterator.h"
32#include "Frame.h"
33#include "FrameLoader.h"
34#include "HTMLDocument.h"
35#include "HTMLFormElement.h"
36#include "HTMLImageLoader.h"
37#include "HTMLMetaElement.h"
38#include "HTMLNames.h"
39#include "HTMLParamElement.h"
40#include "HTMLParserIdioms.h"
41#include "MIMETypeRegistry.h"
42#include "NodeList.h"
43#include "Page.h"
44#include "PluginViewBase.h"
45#include "RenderEmbeddedObject.h"
46#include "RenderImage.h"
47#include "RenderWidget.h"
48#include "Settings.h"
49#include "SubframeLoader.h"
50#include "Text.h"
51#include "Widget.h"
52#include <wtf/IsoMallocInlines.h>
53#include <wtf/Ref.h>
54
55#if PLATFORM(IOS_FAMILY)
56#include "RuntimeApplicationChecks.h"
57#include <wtf/spi/darwin/dyldSPI.h>
58#endif
59
60namespace WebCore {
61
62WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLObjectElement);
63
64using namespace HTMLNames;
65
66inline HTMLObjectElement::HTMLObjectElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
67 : HTMLPlugInImageElement(tagName, document)
68 , FormAssociatedElement(form)
69{
70 ASSERT(hasTagName(objectTag));
71}
72
73Ref<HTMLObjectElement> HTMLObjectElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form)
74{
75 auto result = adoptRef(*new HTMLObjectElement(tagName, document, form));
76 result->finishCreating();
77 return result;
78}
79
80RenderWidget* HTMLObjectElement::renderWidgetLoadingPlugin() const
81{
82 // Needs to load the plugin immediatedly because this function is called
83 // when JavaScript code accesses the plugin.
84 // FIXME: <rdar://16893708> Check if dispatching events here is safe.
85 document().updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasks::Synchronously);
86 return renderWidget(); // This will return 0 if the renderer is not a RenderWidget.
87}
88
89bool HTMLObjectElement::isPresentationAttribute(const QualifiedName& name) const
90{
91 if (name == borderAttr)
92 return true;
93 return HTMLPlugInImageElement::isPresentationAttribute(name);
94}
95
96void HTMLObjectElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
97{
98 if (name == borderAttr)
99 applyBorderAttributeToStyle(value, style);
100 else
101 HTMLPlugInImageElement::collectStyleForPresentationAttribute(name, value, style);
102}
103
104void HTMLObjectElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
105{
106 bool invalidateRenderer = false;
107
108 if (name == formAttr)
109 formAttributeChanged();
110 else if (name == typeAttr) {
111 m_serviceType = value.string().left(value.find(';')).convertToASCIILowercase();
112 invalidateRenderer = !hasAttributeWithoutSynchronization(classidAttr);
113 setNeedsWidgetUpdate(true);
114 } else if (name == dataAttr) {
115 m_url = stripLeadingAndTrailingHTMLSpaces(value);
116 invalidateRenderer = !hasAttributeWithoutSynchronization(classidAttr);
117 setNeedsWidgetUpdate(true);
118 updateImageLoaderWithNewURLSoon();
119 } else if (name == classidAttr) {
120 invalidateRenderer = true;
121 setNeedsWidgetUpdate(true);
122 } else
123 HTMLPlugInImageElement::parseAttribute(name, value);
124
125 if (!invalidateRenderer || !isConnected() || !renderer())
126 return;
127
128 m_useFallbackContent = false;
129 scheduleUpdateForAfterStyleResolution();
130 invalidateStyleAndRenderersForSubtree();
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 require "src" attribute).
136 bool foundSrcParam = false;
137 String dataParamValue;
138 for (unsigned i = 0; i < paramNames.size(); ++i) {
139 if (equalLettersIgnoringASCIICase(paramNames[i], "src"))
140 foundSrcParam = true;
141 else if (equalLettersIgnoringASCIICase(paramNames[i], "data"))
142 dataParamValue = paramValues[i];
143 }
144 if (!foundSrcParam && !dataParamValue.isNull()) {
145 paramNames.append("src"_s);
146 paramValues.append(WTFMove(dataParamValue));
147 }
148}
149
150// FIXME: This function should not deal with url or serviceType!
151void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType)
152{
153 HashSet<StringImpl*, ASCIICaseInsensitiveHash> uniqueParamNames;
154 String urlParameter;
155
156 // Scan the PARAM children and store their name/value pairs.
157 // Get the URL and type from the params if we don't already have them.
158 for (auto& param : childrenOfType<HTMLParamElement>(*this)) {
159 String name = param.name();
160 if (name.isEmpty())
161 continue;
162
163 uniqueParamNames.add(name.impl());
164 paramNames.append(param.name());
165 paramValues.append(param.value());
166
167 // FIXME: url adjustment does not belong in this function.
168 if (url.isEmpty() && urlParameter.isEmpty() && (equalLettersIgnoringASCIICase(name, "src") || equalLettersIgnoringASCIICase(name, "movie") || equalLettersIgnoringASCIICase(name, "code") || equalLettersIgnoringASCIICase(name, "url")))
169 urlParameter = stripLeadingAndTrailingHTMLSpaces(param.value());
170 // FIXME: serviceType calculation does not belong in this function.
171 if (serviceType.isEmpty() && equalLettersIgnoringASCIICase(name, "type")) {
172 serviceType = param.value();
173 size_t pos = serviceType.find(';');
174 if (pos != notFound)
175 serviceType = serviceType.left(pos);
176 }
177 }
178
179 // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag
180 // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is
181 // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means
182 // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM,
183 // else our Java plugin will misinterpret it. [4004531]
184 String codebase;
185 if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) {
186 codebase = "codebase";
187 uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already
188 }
189
190 // Turn the attributes of the <object> element into arrays, but don't override <param> values.
191 if (hasAttributes()) {
192 for (const Attribute& attribute : attributesIterator()) {
193 const AtomicString& name = attribute.name().localName();
194 if (!uniqueParamNames.contains(name.impl())) {
195 paramNames.append(name.string());
196 paramValues.append(attribute.value().string());
197 }
198 }
199 }
200
201 mapDataParamToSrc(paramNames, paramValues);
202
203 // HTML5 says that an object resource's URL is specified by the object's data
204 // attribute, not by a param element. However, for compatibility, allow the
205 // resource's URL to be given by a param named "src", "movie", "code" or "url"
206 // if we know that resource points to a plug-in.
207
208 if (url.isEmpty() && !urlParameter.isEmpty()) {
209 SubframeLoader& loader = document().frame()->loader().subframeLoader();
210 if (loader.resourceWillUsePlugin(urlParameter, serviceType))
211 url = urlParameter;
212 }
213}
214
215bool HTMLObjectElement::hasFallbackContent() const
216{
217 for (RefPtr<Node> child = firstChild(); child; child = child->nextSibling()) {
218 // Ignore whitespace-only text, and <param> tags, any other content is fallback content.
219 if (is<Text>(*child)) {
220 if (!downcast<Text>(*child).data().isAllSpecialCharacters<isHTMLSpace>())
221 return true;
222 } else if (!is<HTMLParamElement>(*child))
223 return true;
224 }
225 return false;
226}
227
228bool HTMLObjectElement::hasValidClassId()
229{
230 if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType()) && protocolIs(attributeWithoutSynchronization(classidAttr), "java"))
231 return true;
232
233 // HTML5 says that fallback content should be rendered if a non-empty
234 // classid is specified for which the UA can't find a suitable plug-in.
235 return attributeWithoutSynchronization(classidAttr).isEmpty();
236}
237
238// FIXME: This should be unified with HTMLEmbedElement::updateWidget and
239// moved down into HTMLPluginImageElement.cpp
240void HTMLObjectElement::updateWidget(CreatePlugins createPlugins)
241{
242 ASSERT(!renderEmbeddedObject()->isPluginUnavailable());
243 ASSERT(needsWidgetUpdate());
244
245 // FIXME: This should ASSERT isFinishedParsingChildren() instead.
246 if (!isFinishedParsingChildren()) {
247 setNeedsWidgetUpdate(false);
248 return;
249 }
250
251 // FIXME: I'm not sure it's ever possible to get into updateWidget during a
252 // removal, but just in case we should avoid loading the frame to prevent
253 // security bugs.
254 if (!SubframeLoadingDisabler::canLoadFrame(*this)) {
255 setNeedsWidgetUpdate(false);
256 return;
257 }
258
259 String url = this->url();
260 String serviceType = this->serviceType();
261
262 // FIXME: These should be joined into a PluginParameters class.
263 Vector<String> paramNames;
264 Vector<String> paramValues;
265 parametersForPlugin(paramNames, paramValues, url, serviceType);
266
267 // Note: url is modified above by parametersForPlugin.
268 if (!allowedToLoadFrameURL(url)) {
269 setNeedsWidgetUpdate(false);
270 return;
271 }
272
273 // FIXME: It's sadness that we have this special case here.
274 // See http://trac.webkit.org/changeset/25128 and
275 // plugins/netscape-plugin-setwindow-size.html
276 if (createPlugins == CreatePlugins::No && wouldLoadAsPlugIn(url, serviceType))
277 return;
278
279 setNeedsWidgetUpdate(false);
280
281 Ref<HTMLObjectElement> protectedThis(*this); // beforeload and plugin loading can make arbitrary DOM mutations.
282 bool beforeLoadAllowedLoad = guardedDispatchBeforeLoadEvent(url);
283 if (!renderer()) // Do not load the plugin if beforeload removed this element or its renderer.
284 return;
285
286 bool success = beforeLoadAllowedLoad && hasValidClassId() && allowedToLoadFrameURL(url);
287 if (success)
288 success = requestObject(url, serviceType, paramNames, paramValues);
289 if (!success && hasFallbackContent())
290 renderFallbackContent();
291}
292
293Node::InsertedIntoAncestorResult HTMLObjectElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
294{
295 HTMLPlugInImageElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
296 FormAssociatedElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
297 return InsertedIntoAncestorResult::NeedsPostInsertionCallback;
298}
299
300void HTMLObjectElement::didFinishInsertingNode()
301{
302 resetFormOwner();
303}
304
305void HTMLObjectElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
306{
307 HTMLPlugInImageElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
308 FormAssociatedElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
309}
310
311void HTMLObjectElement::childrenChanged(const ChildChange& change)
312{
313 updateExposedState();
314 if (isConnected() && !m_useFallbackContent) {
315 setNeedsWidgetUpdate(true);
316 scheduleUpdateForAfterStyleResolution();
317 invalidateStyleForSubtree();
318 }
319 HTMLPlugInImageElement::childrenChanged(change);
320}
321
322bool HTMLObjectElement::isURLAttribute(const Attribute& attribute) const
323{
324 return attribute.name() == dataAttr || attribute.name() == codebaseAttr || (attribute.name() == usemapAttr && attribute.value().string()[0] != '#') || HTMLPlugInImageElement::isURLAttribute(attribute);
325}
326
327const AtomicString& HTMLObjectElement::imageSourceURL() const
328{
329 return attributeWithoutSynchronization(dataAttr);
330}
331
332void HTMLObjectElement::renderFallbackContent()
333{
334 if (m_useFallbackContent)
335 return;
336
337 if (!isConnected())
338 return;
339
340 scheduleUpdateForAfterStyleResolution();
341 invalidateStyleAndRenderersForSubtree();
342
343 // Before we give up and use fallback content, check to see if this is a MIME type issue.
344 auto* loader = imageLoader();
345 if (loader && loader->image() && loader->image()->status() != CachedResource::LoadError) {
346 m_serviceType = loader->image()->response().mimeType();
347 if (!isImageType()) {
348 // If we don't think we have an image type anymore, then clear the image from the loader.
349 loader->clearImage();
350 return;
351 }
352 }
353
354 m_useFallbackContent = true;
355}
356
357static inline bool preventsParentObjectFromExposure(const Element& child)
358{
359 static const auto mostKnownTags = makeNeverDestroyed([] {
360 HashSet<QualifiedName> set;
361 auto* tags = HTMLNames::getHTMLTags();
362 for (size_t i = 0; i < HTMLNames::HTMLTagsCount; i++) {
363 auto& tag = *tags[i];
364 // Only the param element was explicitly mentioned in the HTML specification rule
365 // we were trying to implement, but these are other known HTML elements that we
366 // have decided, over the years, to treat as children that do not prevent object
367 // names from being exposed.
368 if (tag == bgsoundTag
369 || tag == commandTag
370 || tag == detailsTag
371 || tag == figcaptionTag
372 || tag == figureTag
373 || tag == paramTag
374 || tag == summaryTag
375 || tag == trackTag)
376 continue;
377 set.add(tag);
378 }
379 return set;
380 }());
381 return mostKnownTags.get().contains(child.tagQName());
382}
383
384static inline bool preventsParentObjectFromExposure(const Node& child)
385{
386 if (is<Element>(child))
387 return preventsParentObjectFromExposure(downcast<Element>(child));
388 if (is<Text>(child))
389 return !downcast<Text>(child).data().isAllSpecialCharacters<isHTMLSpace>();
390 return true;
391}
392
393static inline bool shouldBeExposed(const HTMLObjectElement& element)
394{
395 // FIXME: This should be redone to use the concept of an exposed object element,
396 // as documented in the HTML specification section describing DOM tree accessors.
397
398 // The rule we try to implement here, from older HTML specifications, is "object elements
399 // with no children other than param elements, unknown elements and whitespace can be found
400 // by name in a document, and other object elements cannot".
401
402 for (auto child = makeRefPtr(element.firstChild()); child; child = child->nextSibling()) {
403 if (preventsParentObjectFromExposure(*child))
404 return false;
405 }
406 return true;
407}
408
409void HTMLObjectElement::updateExposedState()
410{
411 bool wasExposed = std::exchange(m_isExposed, shouldBeExposed(*this));
412
413 if (m_isExposed != wasExposed && isConnected() && !isInShadowTree() && is<HTMLDocument>(document())) {
414 auto& document = downcast<HTMLDocument>(this->document());
415
416 auto& id = getIdAttribute();
417 if (!id.isEmpty()) {
418 if (m_isExposed)
419 document.addDocumentNamedItem(*id.impl(), *this);
420 else
421 document.removeDocumentNamedItem(*id.impl(), *this);
422 }
423
424 auto& name = getNameAttribute();
425 if (!name.isEmpty() && id != name) {
426 if (m_isExposed)
427 document.addDocumentNamedItem(*name.impl(), *this);
428 else
429 document.removeDocumentNamedItem(*name.impl(), *this);
430 }
431 }
432}
433
434bool HTMLObjectElement::containsJavaApplet() const
435{
436 if (MIMETypeRegistry::isJavaAppletMIMEType(attributeWithoutSynchronization(typeAttr)))
437 return true;
438
439 for (auto& child : childrenOfType<Element>(*this)) {
440 if (child.hasTagName(paramTag) && equalLettersIgnoringASCIICase(child.getNameAttribute(), "type")
441 && MIMETypeRegistry::isJavaAppletMIMEType(child.attributeWithoutSynchronization(valueAttr).string()))
442 return true;
443 if (child.hasTagName(objectTag) && downcast<HTMLObjectElement>(child).containsJavaApplet())
444 return true;
445 if (child.hasTagName(appletTag))
446 return true;
447 }
448
449 return false;
450}
451
452void HTMLObjectElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
453{
454 HTMLPlugInImageElement::addSubresourceAttributeURLs(urls);
455
456 addSubresourceURL(urls, document().completeURL(attributeWithoutSynchronization(dataAttr)));
457
458 // FIXME: Passing a string that starts with "#" to the completeURL function does
459 // not seem like it would work. The image element has similar but not identical code.
460 const AtomicString& useMap = attributeWithoutSynchronization(usemapAttr);
461 if (useMap.startsWith('#'))
462 addSubresourceURL(urls, document().completeURL(useMap));
463}
464
465void HTMLObjectElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
466{
467 FormAssociatedElement::didMoveToNewDocument(oldDocument);
468 HTMLPlugInImageElement::didMoveToNewDocument(oldDocument, newDocument);
469}
470
471bool HTMLObjectElement::appendFormData(DOMFormData& formData, bool)
472{
473 if (name().isEmpty())
474 return false;
475
476 // Use PluginLoadingPolicy::DoNotLoad here or it would fire JS events synchronously
477 // which would not be safe here.
478 auto widget = makeRefPtr(pluginWidget(PluginLoadingPolicy::DoNotLoad));
479 if (!is<PluginViewBase>(widget))
480 return false;
481 String value;
482 if (!downcast<PluginViewBase>(*widget).getFormValue(value))
483 return false;
484 formData.append(name(), value);
485 return true;
486}
487
488bool HTMLObjectElement::canContainRangeEndPoint() const
489{
490 // Call through to HTMLElement because HTMLPlugInElement::canContainRangeEndPoint
491 // returns false unconditionally. An object element using fallback content is
492 // treated like a generic HTML element.
493 return m_useFallbackContent && HTMLElement::canContainRangeEndPoint();
494}
495
496}
497