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