Replace some auto* with RefPtr within WebCore/html
[WebKit-https.git] / Source / WebCore / html / MediaDocument.cpp
1 /*
2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "MediaDocument.h"
28
29 #if ENABLE(VIDEO)
30
31 #include "Chrome.h"
32 #include "ChromeClient.h"
33 #include "DocumentLoader.h"
34 #include "EventNames.h"
35 #include "Frame.h"
36 #include "FrameLoader.h"
37 #include "FrameLoaderClient.h"
38 #include "HTMLBodyElement.h"
39 #include "HTMLEmbedElement.h"
40 #include "HTMLHeadElement.h"
41 #include "HTMLHtmlElement.h"
42 #include "HTMLMetaElement.h"
43 #include "HTMLNames.h"
44 #include "HTMLSourceElement.h"
45 #include "HTMLVideoElement.h"
46 #include "KeyboardEvent.h"
47 #include "NodeList.h"
48 #include "Page.h"
49 #include "RawDataDocumentParser.h"
50 #include "RuntimeEnabledFeatures.h"
51 #include "ScriptController.h"
52 #include "ShadowRoot.h"
53 #include "TypedElementDescendantIterator.h"
54 #include <wtf/text/StringBuilder.h>
55
56 namespace WebCore {
57
58 using namespace HTMLNames;
59
60 // FIXME: Share more code with PluginDocumentParser.
61 class MediaDocumentParser final : public RawDataDocumentParser {
62 public:
63     static Ref<MediaDocumentParser> create(MediaDocument& document)
64     {
65         return adoptRef(*new MediaDocumentParser(document));
66     }
67     
68 private:
69     MediaDocumentParser(MediaDocument& document)
70         : RawDataDocumentParser { document }
71         , m_outgoingReferrer { document.outgoingReferrer() }
72     {
73     }
74
75     void appendBytes(DocumentWriter&, const char*, size_t) final;
76     void createDocumentStructure();
77
78     HTMLMediaElement* m_mediaElement { nullptr };
79     String m_outgoingReferrer;
80 };
81     
82 void MediaDocumentParser::createDocumentStructure()
83 {
84     auto& document = *this->document();
85
86     auto rootElement = HTMLHtmlElement::create(document);
87     document.appendChild(rootElement);
88     document.setCSSTarget(rootElement.ptr());
89     rootElement->insertedByParser();
90
91     if (document.frame())
92         document.frame()->injectUserScripts(InjectAtDocumentStart);
93
94 #if PLATFORM(IOS)
95     auto headElement = HTMLHeadElement::create(document);
96     rootElement->appendChild(headElement);
97
98     auto metaElement = HTMLMetaElement::create(document);
99     metaElement->setAttributeWithoutSynchronization(nameAttr, AtomicString("viewport", AtomicString::ConstructFromLiteral));
100     metaElement->setAttributeWithoutSynchronization(contentAttr, AtomicString("width=device-width,initial-scale=1", AtomicString::ConstructFromLiteral));
101     headElement->appendChild(metaElement);
102 #endif
103
104     auto body = HTMLBodyElement::create(document);
105     rootElement->appendChild(body);
106
107     auto videoElement = HTMLVideoElement::create(document);
108     m_mediaElement = videoElement.ptr();
109     videoElement->setAttributeWithoutSynchronization(controlsAttr, emptyAtom());
110     videoElement->setAttributeWithoutSynchronization(autoplayAttr, emptyAtom());
111     videoElement->setAttributeWithoutSynchronization(srcAttr, document.url().string());
112     if (auto loader = makeRefPtr(document.loader()))
113         videoElement->setAttributeWithoutSynchronization(typeAttr, loader->responseMIMEType());
114
115     if (!RuntimeEnabledFeatures::sharedFeatures().modernMediaControlsEnabled()) {
116         StringBuilder elementStyle;
117         elementStyle.appendLiteral("max-width: 100%; max-height: 100%;");
118 #if PLATFORM(IOS)
119         elementStyle.appendLiteral("width: 100%; height: auto;");
120 #endif
121         videoElement->setAttribute(styleAttr, elementStyle.toString());
122     }
123
124     body->appendChild(videoElement);
125
126     RefPtr<Frame> frame = document.frame();
127     if (!frame)
128         return;
129
130     frame->loader().activeDocumentLoader()->setMainResourceDataBufferingPolicy(DoNotBufferData);
131     frame->loader().setOutgoingReferrer(document.completeURL(m_outgoingReferrer));
132 }
133
134 void MediaDocumentParser::appendBytes(DocumentWriter&, const char*, size_t)
135 {
136     if (m_mediaElement)
137         return;
138
139     createDocumentStructure();
140     finish();
141 }
142     
143 MediaDocument::MediaDocument(Frame* frame, const URL& url)
144     : HTMLDocument(frame, url, MediaDocumentClass)
145     , m_replaceMediaElementTimer(*this, &MediaDocument::replaceMediaElementTimerFired)
146 {
147     setCompatibilityMode(DocumentCompatibilityMode::QuirksMode);
148     lockCompatibilityMode();
149     if (frame)
150         m_outgoingReferrer = frame->loader().outgoingReferrer();
151 }
152
153 MediaDocument::~MediaDocument()
154 {
155     ASSERT(!m_replaceMediaElementTimer.isActive());
156 }
157
158 Ref<DocumentParser> MediaDocument::createParser()
159 {
160     return MediaDocumentParser::create(*this);
161 }
162
163 static inline HTMLVideoElement* descendantVideoElement(ContainerNode& node)
164 {
165     if (is<HTMLVideoElement>(node))
166         return downcast<HTMLVideoElement>(&node);
167
168     return descendantsOfType<HTMLVideoElement>(node).first();
169 }
170
171 static inline HTMLVideoElement* ancestorVideoElement(Node* node)
172 {
173     while (node && !is<HTMLVideoElement>(*node))
174         node = node->parentOrShadowHostNode();
175
176     return downcast<HTMLVideoElement>(node);
177 }
178
179 void MediaDocument::defaultEventHandler(Event& event)
180 {
181     // Modern media controls have their own event handling to determine when to
182     // pause or resume playback.
183     if (RuntimeEnabledFeatures::sharedFeatures().modernMediaControlsEnabled())
184         return;
185     
186     // Match the default Quicktime plugin behavior to allow
187     // clicking and double-clicking to pause and play the media.
188     auto targetNode = event.target()->toNode();
189     if (!targetNode)
190         return;
191
192     if (RefPtr<HTMLVideoElement> video = ancestorVideoElement(targetNode.get())) {
193         if (event.type() == eventNames().clickEvent) {
194             if (!video->canPlay()) {
195                 video->pause();
196                 event.setDefaultHandled();
197             }
198         } else if (event.type() == eventNames().dblclickEvent) {
199             if (video->canPlay()) {
200                 video->play();
201                 event.setDefaultHandled();
202             }
203         }
204     }
205
206     if (!is<ContainerNode>(*targetNode))
207         return;
208     ContainerNode& targetContainer = downcast<ContainerNode>(*targetNode);
209     if (event.type() == eventNames().keydownEvent && is<KeyboardEvent>(event)) {
210         RefPtr<HTMLVideoElement> video = descendantVideoElement(targetContainer);
211         if (!video)
212             return;
213
214         KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(event);
215         if (keyboardEvent.keyIdentifier() == "U+0020") { // space
216             if (video->paused()) {
217                 if (video->canPlay())
218                     video->play();
219             } else
220                 video->pause();
221             keyboardEvent.setDefaultHandled();
222         }
223     }
224 }
225
226 void MediaDocument::mediaElementSawUnsupportedTracks()
227 {
228     // The HTMLMediaElement was told it has something that the underlying 
229     // MediaPlayer cannot handle so we should switch from <video> to <embed> 
230     // and let the plugin handle this. Don't do it immediately as this 
231     // function may be called directly from a media engine callback, and 
232     // replaceChild will destroy the element, media player, and media engine.
233     m_replaceMediaElementTimer.startOneShot(0_s);
234 }
235
236 void MediaDocument::replaceMediaElementTimerFired()
237 {
238     auto htmlBody = makeRefPtr(bodyOrFrameset());
239     if (!htmlBody)
240         return;
241
242     // Set body margin width and height to 0 as that is what a PluginDocument uses.
243     htmlBody->setAttributeWithoutSynchronization(marginwidthAttr, AtomicString("0", AtomicString::ConstructFromLiteral));
244     htmlBody->setAttributeWithoutSynchronization(marginheightAttr, AtomicString("0", AtomicString::ConstructFromLiteral));
245
246     if (auto videoElement = makeRefPtr(descendantVideoElement(*htmlBody))) {
247         auto embedElement = HTMLEmbedElement::create(*this);
248
249         embedElement->setAttributeWithoutSynchronization(widthAttr, AtomicString("100%", AtomicString::ConstructFromLiteral));
250         embedElement->setAttributeWithoutSynchronization(heightAttr, AtomicString("100%", AtomicString::ConstructFromLiteral));
251         embedElement->setAttributeWithoutSynchronization(nameAttr, AtomicString("plugin", AtomicString::ConstructFromLiteral));
252         embedElement->setAttributeWithoutSynchronization(srcAttr, url().string());
253
254         ASSERT(loader());
255         if (auto loader = makeRefPtr(this->loader()))
256             embedElement->setAttributeWithoutSynchronization(typeAttr, loader->writer().mimeType());
257
258         videoElement->parentNode()->replaceChild(embedElement, *videoElement);
259     }
260 }
261
262 void MediaDocument::mediaElementNaturalSizeChanged(const IntSize& newSize)
263 {
264     if (ownerElement())
265         return;
266
267     if (newSize.isZero())
268         return;
269
270     if (page())
271         page()->chrome().client().imageOrMediaDocumentSizeChanged(newSize);
272 }
273
274 }
275
276 #endif