HTMLFrameOwnerElement should obey the SubframeLoadingDisabler when creating subframes
[WebKit-https.git] / Source / WebCore / loader / SubframeLoader.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
4  * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
5  * Copyright (C) 2008 Alp Toker <alp@atoker.com>
6  * Copyright (C) Research In Motion Limited 2009. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1.  Redistributions of source code must retain the above copyright
13  *     notice, this list of conditions and the following disclaimer. 
14  * 2.  Redistributions in binary form must reproduce the above copyright
15  *     notice, this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution. 
17  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
18  *     its contributors may be used to endorse or promote products derived
19  *     from this software without specific prior written permission. 
20  *
21  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
22  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
25  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "config.h"
34 #include "SubframeLoader.h"
35
36 #include "Chrome.h"
37 #include "ChromeClient.h"
38 #include "ContentSecurityPolicy.h"
39 #include "DiagnosticLoggingKeys.h"
40 #include "Frame.h"
41 #include "FrameLoader.h"
42 #include "FrameLoaderClient.h"
43 #include "HTMLAppletElement.h"
44 #include "HTMLAudioElement.h"
45 #include "HTMLFrameElementBase.h"
46 #include "HTMLNames.h"
47 #include "HTMLObjectElement.h"
48 #include "MIMETypeRegistry.h"
49 #include "Page.h"
50 #include "PluginData.h"
51 #include "PluginDocument.h"
52 #include "RenderEmbeddedObject.h"
53 #include "RenderView.h"
54 #include "ScriptController.h"
55 #include "SecurityOrigin.h"
56 #include "SecurityPolicy.h"
57 #include "Settings.h"
58
59 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
60 #include "HTMLMediaElement.h"
61 #include "RenderVideo.h"
62 #endif
63
64 namespace WebCore {
65     
66 using namespace HTMLNames;
67
68 SubframeLoader::SubframeLoader(Frame& frame)
69     : m_containsPlugins(false)
70     , m_frame(frame)
71 {
72 }
73
74 void SubframeLoader::clear()
75 {
76     m_containsPlugins = false;
77 }
78
79 bool SubframeLoader::requestFrame(HTMLFrameOwnerElement& ownerElement, const String& urlString, const AtomicString& frameName, bool lockHistory, bool lockBackForwardList)
80 {
81     // Support for <frame src="javascript:string">
82     URL scriptURL;
83     URL url;
84     if (protocolIsJavaScript(urlString)) {
85         scriptURL = completeURL(urlString); // completeURL() encodes the URL.
86         url = blankURL();
87     } else
88         url = completeURL(urlString);
89
90     Frame* frame = loadOrRedirectSubframe(ownerElement, url, frameName, lockHistory, lockBackForwardList);
91     if (!frame)
92         return false;
93
94     if (!scriptURL.isEmpty())
95         frame->script().executeIfJavaScriptURL(scriptURL);
96
97     return true;
98 }
99     
100 bool SubframeLoader::resourceWillUsePlugin(const String& url, const String& mimeType, bool shouldPreferPlugInsForImages)
101 {
102     URL completedURL;
103     if (!url.isEmpty())
104         completedURL = completeURL(url);
105
106     bool useFallback;
107     return shouldUsePlugin(completedURL, mimeType, shouldPreferPlugInsForImages, false, useFallback);
108 }
109
110 bool SubframeLoader::pluginIsLoadable(HTMLPlugInImageElement& pluginElement, const URL& url, const String& mimeType)
111 {
112     if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType)) {
113         if (!m_frame.settings().isJavaEnabled())
114             return false;
115         if (document() && document()->securityOrigin()->isLocal() && !m_frame.settings().isJavaEnabledForLocalFiles())
116             return false;
117     }
118
119     if (document()) {
120         if (document()->isSandboxed(SandboxPlugins))
121             return false;
122
123         if (!document()->securityOrigin()->canDisplay(url)) {
124             FrameLoader::reportLocalLoadFailed(&m_frame, url.string());
125             return false;
126         }
127
128         String declaredMimeType = document()->isPluginDocument() && document()->ownerElement() ?
129             document()->ownerElement()->fastGetAttribute(HTMLNames::typeAttr) :
130             pluginElement.fastGetAttribute(HTMLNames::typeAttr);
131         if (!document()->contentSecurityPolicy()->allowObjectFromSource(url)
132             || !document()->contentSecurityPolicy()->allowPluginType(mimeType, declaredMimeType, url)) {
133             RenderEmbeddedObject* renderer = pluginElement.renderEmbeddedObject();
134             renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy);
135             return false;
136         }
137
138         if (!m_frame.loader().mixedContentChecker().canRunInsecureContent(document()->securityOrigin(), url))
139             return false;
140     }
141
142     return true;
143 }
144
145 bool SubframeLoader::requestPlugin(HTMLPlugInImageElement& ownerElement, const URL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback)
146 {
147     // Application plug-ins are plug-ins implemented by the user agent, for example Qt plug-ins,
148     // as opposed to third-party code such as Flash. The user agent decides whether or not they are
149     // permitted, rather than WebKit.
150     if ((!allowPlugins(AboutToInstantiatePlugin) && !MIMETypeRegistry::isApplicationPluginMIMEType(mimeType)))
151         return false;
152
153     if (!pluginIsLoadable(ownerElement, url, mimeType))
154         return false;
155
156     ASSERT(ownerElement.hasTagName(objectTag) || ownerElement.hasTagName(embedTag));
157     return loadPlugin(ownerElement, url, mimeType, paramNames, paramValues, useFallback);
158 }
159
160 static String findPluginMIMETypeFromURL(Page* page, const String& url)
161 {
162     if (!url)
163         return String();
164
165     size_t dotIndex = url.reverseFind('.');
166     if (dotIndex == notFound)
167         return String();
168
169     String extension = url.substring(dotIndex + 1);
170
171     const PluginData& pluginData = page->pluginData();
172
173     for (size_t i = 0; i < pluginData.mimes().size(); ++i) {
174         const MimeClassInfo& mimeClassInfo = pluginData.mimes()[i];
175         for (size_t j = 0; j < mimeClassInfo.extensions.size(); ++j) {
176             if (equalIgnoringCase(extension, mimeClassInfo.extensions[j]))
177                 return mimeClassInfo.type;
178         }
179     }
180
181     return String();
182 }
183
184 static void logPluginRequest(Page* page, const String& mimeType, const String& url, bool success)
185 {
186     if (!page || !page->settings().diagnosticLoggingEnabled())
187         return;
188
189     String newMIMEType = mimeType;
190     if (!newMIMEType) {
191         // Try to figure out the MIME type from the URL extension.
192         newMIMEType = findPluginMIMETypeFromURL(page, url);
193         if (!newMIMEType)
194             return;
195     }
196
197     String pluginFile = page->pluginData().pluginFileForMimeType(newMIMEType);
198     String description = !pluginFile ? newMIMEType : pluginFile;
199
200     ChromeClient& chromeClient = page->chrome().client();
201     chromeClient.logDiagnosticMessage(success ? DiagnosticLoggingKeys::pluginLoadedKey() : DiagnosticLoggingKeys::pluginLoadingFailedKey(), description, DiagnosticLoggingKeys::noopKey());
202
203     if (!page->hasSeenAnyPlugin())
204         chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOnePluginKey(), emptyString(), DiagnosticLoggingKeys::noopKey());
205     
206     if (!page->hasSeenPlugin(description))
207         chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsPluginKey(), description, DiagnosticLoggingKeys::noopKey());
208
209     page->sawPlugin(description);
210 }
211
212 bool SubframeLoader::requestObject(HTMLPlugInImageElement& ownerElement, const String& url, const AtomicString& frameName, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
213 {
214     if (url.isEmpty() && mimeType.isEmpty())
215         return false;
216
217     URL completedURL;
218     if (!url.isEmpty())
219         completedURL = completeURL(url);
220
221     bool hasFallbackContent = isHTMLObjectElement(ownerElement) && toHTMLObjectElement(ownerElement).hasFallbackContent();
222
223     bool useFallback;
224     if (shouldUsePlugin(completedURL, mimeType, ownerElement.shouldPreferPlugInsForImages(), hasFallbackContent, useFallback)) {
225         bool success = requestPlugin(ownerElement, completedURL, mimeType, paramNames, paramValues, useFallback);
226         logPluginRequest(document()->page(), mimeType, completedURL, success);
227         return success;
228     }
229
230     // If the plug-in element already contains a subframe, loadOrRedirectSubframe will re-use it. Otherwise,
231     // it will create a new frame and set it as the RenderWidget's Widget, causing what was previously 
232     // in the widget to be torn down.
233     return loadOrRedirectSubframe(ownerElement, completedURL, frameName, true, true);
234 }
235
236 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
237 PassRefPtr<Widget> SubframeLoader::loadMediaPlayerProxyPlugin(HTMLMediaElement& mediaElement, const URL& url, const Vector<String>& paramNames, const Vector<String>& paramValues)
238 {
239     ASSERT(mediaElement.hasTagName(videoTag) || isHTMLAudioElement(mediaElement));
240
241     URL completedURL;
242     if (!url.isEmpty())
243         completedURL = completeURL(url);
244
245     if (!m_frame.document()->securityOrigin()->canDisplay(completedURL)) {
246         FrameLoader::reportLocalLoadFailed(&m_frame, completedURL.string());
247         return nullptr;
248     }
249
250     if (!m_frame.document()->contentSecurityPolicy()->allowMediaFromSource(completedURL))
251         return nullptr;
252
253     RenderWidget* renderer = toRenderWidget(mediaElement.renderer());
254     IntSize size;
255
256     if (renderer)
257         size = roundedIntSize(LayoutSize(renderer->contentWidth(), renderer->contentHeight()));
258     else if (mediaElement.isVideo())
259         size = RenderVideo::defaultSize();
260
261     if (!m_frame.loader().mixedContentChecker().canRunInsecureContent(m_frame.document()->securityOrigin(), completedURL))
262         return nullptr;
263
264     RefPtr<Widget> widget = m_frame.loader().client().createMediaPlayerProxyPlugin(size, &mediaElement, completedURL, paramNames, paramValues, "application/x-media-element-proxy-plugin");
265
266     if (widget && renderer) {
267         renderer->setWidget(widget);
268         renderer->frameOwnerElement().setNeedsStyleRecalc(SyntheticStyleChange);
269     }
270     m_containsPlugins = true;
271
272     return widget ? widget.release() : nullptr;
273 }
274 #endif // ENABLE(PLUGIN_PROXY_FOR_VIDEO)
275
276 PassRefPtr<Widget> SubframeLoader::createJavaAppletWidget(const IntSize& size, HTMLAppletElement& element, const Vector<String>& paramNames, const Vector<String>& paramValues)
277 {
278     String baseURLString;
279     String codeBaseURLString;
280
281     for (size_t i = 0; i < paramNames.size(); ++i) {
282         if (equalIgnoringCase(paramNames[i], "baseurl"))
283             baseURLString = paramValues[i];
284         else if (equalIgnoringCase(paramNames[i], "codebase"))
285             codeBaseURLString = paramValues[i];
286     }
287
288     if (!codeBaseURLString.isEmpty()) {
289         URL codeBaseURL = completeURL(codeBaseURLString);
290         if (!element.document().securityOrigin()->canDisplay(codeBaseURL)) {
291             FrameLoader::reportLocalLoadFailed(&m_frame, codeBaseURL.string());
292             return nullptr;
293         }
294
295         const char javaAppletMimeType[] = "application/x-java-applet";
296         if (!element.document().contentSecurityPolicy()->allowObjectFromSource(codeBaseURL)
297             || !element.document().contentSecurityPolicy()->allowPluginType(javaAppletMimeType, javaAppletMimeType, codeBaseURL))
298             return nullptr;
299     }
300
301     if (baseURLString.isEmpty())
302         baseURLString = m_frame.document()->baseURL().string();
303     URL baseURL = completeURL(baseURLString);
304
305     RefPtr<Widget> widget;
306     if (allowPlugins(AboutToInstantiatePlugin))
307         widget = m_frame.loader().client().createJavaAppletWidget(size, &element, baseURL, paramNames, paramValues);
308
309     logPluginRequest(document()->page(), element.serviceType(), String(), widget);
310
311     if (!widget) {
312         RenderEmbeddedObject* renderer = element.renderEmbeddedObject();
313
314         if (!renderer->isPluginUnavailable())
315             renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
316         return nullptr;
317     }
318
319     m_containsPlugins = true;
320     return widget;
321 }
322
323 Frame* SubframeLoader::loadOrRedirectSubframe(HTMLFrameOwnerElement& ownerElement, const URL& url, const AtomicString& frameName, bool lockHistory, bool lockBackForwardList)
324 {
325     Frame* frame = ownerElement.contentFrame();
326     if (frame)
327         frame->navigationScheduler().scheduleLocationChange(m_frame.document()->securityOrigin(), url.string(), m_frame.loader().outgoingReferrer(), lockHistory, lockBackForwardList);
328     else
329         frame = loadSubframe(ownerElement, url, frameName, m_frame.loader().outgoingReferrer());
330
331     if (!frame)
332         return nullptr;
333
334     ASSERT(ownerElement.contentFrame() == frame || !ownerElement.contentFrame());
335     return ownerElement.contentFrame();
336 }
337
338 Frame* SubframeLoader::loadSubframe(HTMLFrameOwnerElement& ownerElement, const URL& url, const String& name, const String& referrer)
339 {
340     Ref<Frame> protect(m_frame);
341
342     bool allowsScrolling = true;
343     int marginWidth = -1;
344     int marginHeight = -1;
345     if (ownerElement.hasTagName(frameTag) || ownerElement.hasTagName(iframeTag)) {
346         HTMLFrameElementBase& frameElementBase = toHTMLFrameElementBase(ownerElement);
347         allowsScrolling = frameElementBase.scrollingMode() != ScrollbarAlwaysOff;
348         marginWidth = frameElementBase.marginWidth();
349         marginHeight = frameElementBase.marginHeight();
350     }
351
352     if (!ownerElement.document().securityOrigin()->canDisplay(url)) {
353         FrameLoader::reportLocalLoadFailed(&m_frame, url.string());
354         return nullptr;
355     }
356
357     if (!SubframeLoadingDisabler::canLoadFrame(ownerElement))
358         return nullptr;
359
360     String referrerToUse = SecurityPolicy::generateReferrerHeader(ownerElement.document().referrerPolicy(), url, referrer);
361     RefPtr<Frame> frame = m_frame.loader().client().createFrame(url, name, &ownerElement, referrerToUse, allowsScrolling, marginWidth, marginHeight);
362
363     if (!frame)  {
364         m_frame.loader().checkCallImplicitClose();
365         return nullptr;
366     }
367     
368     // All new frames will have m_isComplete set to true at this point due to synchronously loading
369     // an empty document in FrameLoader::init(). But many frames will now be starting an
370     // asynchronous load of url, so we set m_isComplete to false and then check if the load is
371     // actually completed below. (Note that we set m_isComplete to false even for synchronous
372     // loads, so that checkCompleted() below won't bail early.)
373     // FIXME: Can we remove this entirely? m_isComplete normally gets set to false when a load is committed.
374     frame->loader().started();
375    
376     RenderObject* renderer = ownerElement.renderer();
377     FrameView* view = frame->view();
378     if (renderer && renderer->isWidget() && view)
379         toRenderWidget(renderer)->setWidget(view);
380     
381     m_frame.loader().checkCallImplicitClose();
382     
383     // Some loads are performed synchronously (e.g., about:blank and loads
384     // cancelled by returning a null ResourceRequest from requestFromDelegate).
385     // In these cases, the synchronous load would have finished
386     // before we could connect the signals, so make sure to send the 
387     // completed() signal for the child by hand and mark the load as being
388     // complete.
389     // FIXME: In this case the Frame will have finished loading before 
390     // it's being added to the child list. It would be a good idea to
391     // create the child first, then invoke the loader separately.
392     if (frame->loader().state() == FrameStateComplete && !frame->loader().policyDocumentLoader())
393         frame->loader().checkCompleted();
394
395     return frame.get();
396 }
397
398 bool SubframeLoader::allowPlugins(ReasonForCallingAllowPlugins)
399 {
400     return m_frame.loader().client().allowPlugins(m_frame.settings().arePluginsEnabled());
401 }
402
403 bool SubframeLoader::shouldUsePlugin(const URL& url, const String& mimeType, bool shouldPreferPlugInsForImages, bool hasFallback, bool& useFallback)
404 {
405     if (m_frame.loader().client().shouldAlwaysUsePluginDocument(mimeType)) {
406         useFallback = false;
407         return true;
408     }
409
410     // Allow other plug-ins to win over QuickTime because if the user has installed a plug-in that
411     // can handle TIFF (which QuickTime can also handle) they probably intended to override QT.
412     if (m_frame.page() && (mimeType == "image/tiff" || mimeType == "image/tif" || mimeType == "image/x-tiff")) {
413         String pluginName = m_frame.page()->pluginData().pluginNameForMimeType(mimeType);
414         if (!pluginName.isEmpty() && !pluginName.contains("QuickTime", false)) 
415             return true;
416     }
417         
418     ObjectContentType objectType = m_frame.loader().client().objectContentType(url, mimeType, shouldPreferPlugInsForImages);
419     // If an object's content can't be handled and it has no fallback, let
420     // it be handled as a plugin to show the broken plugin icon.
421     useFallback = objectType == ObjectContentNone && hasFallback;
422     return objectType == ObjectContentNone || objectType == ObjectContentNetscapePlugin || objectType == ObjectContentOtherPlugin;
423 }
424
425 Document* SubframeLoader::document() const
426 {
427     return m_frame.document();
428 }
429
430 bool SubframeLoader::loadPlugin(HTMLPlugInImageElement& pluginElement, const URL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback)
431 {
432     RenderEmbeddedObject* renderer = pluginElement.renderEmbeddedObject();
433
434     // FIXME: This code should not depend on renderer!
435     if (!renderer || useFallback)
436         return false;
437
438     pluginElement.subframeLoaderWillCreatePlugIn(url);
439
440     IntSize contentSize = roundedIntSize(LayoutSize(renderer->contentWidth(), renderer->contentHeight()));
441     bool loadManually = document()->isPluginDocument() && !m_containsPlugins && toPluginDocument(document())->shouldLoadPluginManually();
442
443 #if PLATFORM(IOS)
444     // On iOS, we only tell the plugin to be in full page mode if the containing plugin document is the top level document.
445     if (document()->ownerElement())
446         loadManually = false;
447 #endif
448
449     RefPtr<Widget> widget = m_frame.loader().client().createPlugin(contentSize, &pluginElement, url, paramNames, paramValues, mimeType, loadManually);
450
451     if (!widget) {
452         if (!renderer->isPluginUnavailable())
453             renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
454         return false;
455     }
456
457     pluginElement.subframeLoaderDidCreatePlugIn(widget.get());
458     renderer->setWidget(widget);
459     m_containsPlugins = true;
460  
461 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
462     pluginElement.setNeedsStyleRecalc(SyntheticStyleChange);
463 #endif
464     return true;
465 }
466
467 URL SubframeLoader::completeURL(const String& url) const
468 {
469     ASSERT(m_frame.document());
470     return m_frame.document()->completeURL(url);
471 }
472
473 } // namespace WebCore