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