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
28#if ENABLE(VIDEO)
29#include "MediaDocument.h"
30
31#include "DocumentLoader.h"
32#include "EventNames.h"
33#include "ExceptionCodePlaceholder.h"
34#include "Frame.h"
35#include "FrameLoader.h"
36#include "FrameLoaderClient.h"
37#include "HTMLEmbedElement.h"
38#include "HTMLHtmlElement.h"
39#include "HTMLNames.h"
40#include "HTMLSourceElement.h"
41#include "HTMLVideoElement.h"
42#include "KeyboardEvent.h"
43#include "NodeList.h"
44#include "RawDataDocumentParser.h"
45#include "ScriptController.h"
46#include "ShadowRoot.h"
47
48namespace WebCore {
49
50using namespace HTMLNames;
51
52// FIXME: Share more code with PluginDocumentParser.
53class MediaDocumentParser final : public RawDataDocumentParser {
54public:
55    static PassRefPtr<MediaDocumentParser> create(MediaDocument& document)
56    {
57        return adoptRef(new MediaDocumentParser(document));
58    }
59
60private:
61    MediaDocumentParser(Document& document)
62        : RawDataDocumentParser(document)
63        , m_mediaElement(0)
64    {
65    }
66
67    virtual void appendBytes(DocumentWriter&, const char*, size_t) override;
68
69    void createDocumentStructure();
70
71    HTMLMediaElement* m_mediaElement;
72};
73
74void MediaDocumentParser::createDocumentStructure()
75{
76    RefPtr<Element> rootElement = document()->createElement(htmlTag, false);
77    document()->appendChild(rootElement, IGNORE_EXCEPTION);
78    document()->setCSSTarget(rootElement.get());
79    toHTMLHtmlElement(rootElement.get())->insertedByParser();
80
81    if (document()->frame())
82        document()->frame()->injectUserScripts(InjectAtDocumentStart);
83
84#if PLATFORM(IOS)
85    RefPtr<Element> headElement = document()->createElement(headTag, false);
86    rootElement->appendChild(headElement, IGNORE_EXCEPTION);
87
88    RefPtr<Element> metaElement = document()->createElement(metaTag, false);
89    metaElement->setAttribute(nameAttr, "viewport");
90    metaElement->setAttribute(contentAttr, "width=device-width,initial-scale=1,user-scalable=no");
91    headElement->appendChild(metaElement, IGNORE_EXCEPTION);
92#endif
93
94    RefPtr<Element> body = document()->createElement(bodyTag, false);
95    rootElement->appendChild(body, IGNORE_EXCEPTION);
96
97    RefPtr<Element> mediaElement = document()->createElement(videoTag, false);
98
99    m_mediaElement = toHTMLVideoElement(mediaElement.get());
100    m_mediaElement->setAttribute(controlsAttr, "");
101    m_mediaElement->setAttribute(autoplayAttr, "");
102
103    m_mediaElement->setAttribute(nameAttr, "media");
104
105    StringBuilder elementStyle;
106    elementStyle.appendLiteral("max-width: 100%; max-height: 100%;");
107#if PLATFORM(IOS)
108    elementStyle.appendLiteral("width: 100%; height: 100%;");
109#endif
110    m_mediaElement->setAttribute(styleAttr, elementStyle.toString());
111
112    RefPtr<Element> sourceElement = document()->createElement(sourceTag, false);
113    HTMLSourceElement& source = toHTMLSourceElement(*sourceElement);
114    source.setSrc(document()->url());
115
116    if (DocumentLoader* loader = document()->loader())
117        source.setType(loader->responseMIMEType());
118
119    m_mediaElement->appendChild(sourceElement, IGNORE_EXCEPTION);
120    body->appendChild(mediaElement, IGNORE_EXCEPTION);
121
122    Frame* frame = document()->frame();
123    if (!frame)
124        return;
125
126    frame->loader().activeDocumentLoader()->setMainResourceDataBufferingPolicy(DoNotBufferData);
127}
128
129void MediaDocumentParser::appendBytes(DocumentWriter&, const char*, size_t)
130{
131    if (m_mediaElement)
132        return;
133
134    createDocumentStructure();
135    finish();
136}
137
138MediaDocument::MediaDocument(Frame* frame, const URL& url)
139    : HTMLDocument(frame, url, MediaDocumentClass)
140    , m_replaceMediaElementTimer(this, &MediaDocument::replaceMediaElementTimerFired)
141{
142    setCompatibilityMode(DocumentCompatibilityMode::QuirksMode);
143    lockCompatibilityMode();
144}
145
146MediaDocument::~MediaDocument()
147{
148    ASSERT(!m_replaceMediaElementTimer.isActive());
149}
150
151PassRefPtr<DocumentParser> MediaDocument::createParser()
152{
153    return MediaDocumentParser::create(*this);
154}
155
156static inline HTMLVideoElement* descendentVideoElement(ContainerNode& node)
157{
158    if (isHTMLVideoElement(node))
159        return toHTMLVideoElement(&node);
160
161    RefPtr<NodeList> nodeList = node.getElementsByTagNameNS(videoTag.namespaceURI(), videoTag.localName());
162
163    if (nodeList.get()->length() > 0)
164        return toHTMLVideoElement(nodeList.get()->item(0));
165
166    return 0;
167}
168
169static inline HTMLVideoElement* ancestorVideoElement(Node* node)
170{
171    while (node && !node->hasTagName(videoTag))
172        node = node->parentOrShadowHostNode();
173
174    return toHTMLVideoElement(node);
175}
176
177void MediaDocument::defaultEventHandler(Event* event)
178{
179    // Match the default Quicktime plugin behavior to allow
180    // clicking and double-clicking to pause and play the media.
181    Node* targetNode = event->target()->toNode();
182    if (!targetNode)
183        return;
184
185    if (HTMLVideoElement* video = ancestorVideoElement(targetNode)) {
186        if (event->type() == eventNames().clickEvent) {
187            if (!video->canPlay()) {
188                video->pause();
189                event->setDefaultHandled();
190            }
191        } else if (event->type() == eventNames().dblclickEvent) {
192            if (video->canPlay()) {
193                video->play();
194                event->setDefaultHandled();
195            }
196        }
197    }
198
199    if (!targetNode->isContainerNode())
200        return;
201    ContainerNode& targetContainer = toContainerNode(*targetNode);
202    if (event->type() == eventNames().keydownEvent && event->isKeyboardEvent()) {
203        HTMLVideoElement* video = descendentVideoElement(targetContainer);
204        if (!video)
205            return;
206
207        KeyboardEvent* keyboardEvent = toKeyboardEvent(event);
208        if (keyboardEvent->keyIdentifier() == "U+0020") { // space
209            if (video->paused()) {
210                if (video->canPlay())
211                    video->play();
212            } else
213                video->pause();
214            event->setDefaultHandled();
215        }
216    }
217}
218
219void MediaDocument::mediaElementSawUnsupportedTracks()
220{
221    // The HTMLMediaElement was told it has something that the underlying
222    // MediaPlayer cannot handle so we should switch from <video> to <embed>
223    // and let the plugin handle this. Don't do it immediately as this
224    // function may be called directly from a media engine callback, and
225    // replaceChild will destroy the element, media player, and media engine.
226    m_replaceMediaElementTimer.startOneShot(0);
227}
228
229void MediaDocument::replaceMediaElementTimerFired(Timer<MediaDocument>&)
230{
231    HTMLElement* htmlBody = body();
232    if (!htmlBody)
233        return;
234
235    // Set body margin width and height to 0 as that is what a PluginDocument uses.
236    htmlBody->setAttribute(marginwidthAttr, "0");
237    htmlBody->setAttribute(marginheightAttr, "0");
238
239    if (HTMLVideoElement* videoElement = descendentVideoElement(*htmlBody)) {
240        RefPtr<Element> element = Document::createElement(embedTag, false);
241        HTMLEmbedElement* embedElement = toHTMLEmbedElement(element.get());
242
243        embedElement->setAttribute(widthAttr, "100%");
244        embedElement->setAttribute(heightAttr, "100%");
245        embedElement->setAttribute(nameAttr, "plugin");
246        embedElement->setAttribute(srcAttr, url().string());
247
248        DocumentLoader* documentLoader = loader();
249        ASSERT(documentLoader);
250        if (documentLoader)
251            embedElement->setAttribute(typeAttr, documentLoader->writer().mimeType());
252
253        videoElement->parentNode()->replaceChild(embedElement, videoElement, IGNORE_EXCEPTION);
254    }
255}
256
257}
258#endif
259