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