OLD | NEW |
| (Empty) |
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 "core/html/MediaDocument.h" | |
27 | |
28 #include "bindings/core/v8/AddEventListenerOptionsOrBoolean.h" | |
29 #include "bindings/core/v8/ExceptionState.h" | |
30 #include "core/HTMLNames.h" | |
31 #include "core/dom/DocumentUserGestureToken.h" | |
32 #include "core/dom/ElementTraversal.h" | |
33 #include "core/dom/RawDataDocumentParser.h" | |
34 #include "core/dom/shadow/ShadowRoot.h" | |
35 #include "core/events/Event.h" | |
36 #include "core/events/EventListener.h" | |
37 #include "core/events/KeyboardEvent.h" | |
38 #include "core/frame/LocalFrame.h" | |
39 #include "core/frame/LocalFrameClient.h" | |
40 #include "core/frame/Settings.h" | |
41 #include "core/frame/UseCounter.h" | |
42 #include "core/html/HTMLAnchorElement.h" | |
43 #include "core/html/HTMLBodyElement.h" | |
44 #include "core/html/HTMLContentElement.h" | |
45 #include "core/html/HTMLDivElement.h" | |
46 #include "core/html/HTMLHeadElement.h" | |
47 #include "core/html/HTMLHtmlElement.h" | |
48 #include "core/html/HTMLMetaElement.h" | |
49 #include "core/html/HTMLSourceElement.h" | |
50 #include "core/html/HTMLStyleElement.h" | |
51 #include "core/html/HTMLVideoElement.h" | |
52 #include "core/loader/DocumentLoader.h" | |
53 #include "core/loader/FrameLoader.h" | |
54 #include "platform/Histogram.h" | |
55 #include "platform/KeyboardCodes.h" | |
56 #include "platform/UserGestureIndicator.h" | |
57 #include "platform/text/PlatformLocale.h" | |
58 | |
59 namespace blink { | |
60 | |
61 using namespace HTMLNames; | |
62 | |
63 // Enums used for UMA histogram. | |
64 enum MediaDocumentDownloadButtonValue { | |
65 MediaDocumentDownloadButtonShown, | |
66 MediaDocumentDownloadButtonClicked, | |
67 // Only append new enums here. | |
68 MediaDocumentDownloadButtonMax | |
69 }; | |
70 | |
71 void recordDownloadMetric(MediaDocumentDownloadButtonValue value) { | |
72 DEFINE_STATIC_LOCAL( | |
73 EnumerationHistogram, mediaDocumentDownloadButtonHistogram, | |
74 ("Blink.MediaDocument.DownloadButton", MediaDocumentDownloadButtonMax)); | |
75 mediaDocumentDownloadButtonHistogram.count(value); | |
76 } | |
77 | |
78 // FIXME: Share more code with PluginDocumentParser. | |
79 class MediaDocumentParser : public RawDataDocumentParser { | |
80 public: | |
81 static MediaDocumentParser* create(MediaDocument* document) { | |
82 return new MediaDocumentParser(document); | |
83 } | |
84 | |
85 private: | |
86 explicit MediaDocumentParser(Document* document) | |
87 : RawDataDocumentParser(document), m_didBuildDocumentStructure(false) {} | |
88 | |
89 void appendBytes(const char*, size_t) override; | |
90 | |
91 void createDocumentStructure(); | |
92 | |
93 bool m_didBuildDocumentStructure; | |
94 }; | |
95 | |
96 class MediaDownloadEventListener final : public EventListener { | |
97 public: | |
98 static MediaDownloadEventListener* create() { | |
99 return new MediaDownloadEventListener(); | |
100 } | |
101 | |
102 bool operator==(const EventListener& other) const override { | |
103 return this == &other; | |
104 } | |
105 | |
106 private: | |
107 MediaDownloadEventListener() | |
108 : EventListener(CPPEventListenerType), m_clicked(false) {} | |
109 | |
110 void handleEvent(ExecutionContext* context, Event* event) override { | |
111 if (!m_clicked) { | |
112 recordDownloadMetric(MediaDocumentDownloadButtonClicked); | |
113 m_clicked = true; | |
114 } | |
115 } | |
116 | |
117 bool m_clicked; | |
118 }; | |
119 | |
120 class MediaLoadedEventListener final : public EventListener { | |
121 WTF_MAKE_NONCOPYABLE(MediaLoadedEventListener); | |
122 | |
123 public: | |
124 static MediaLoadedEventListener* create() { | |
125 return new MediaLoadedEventListener(); | |
126 } | |
127 | |
128 bool operator==(const EventListener& other) const override { | |
129 return this == &other; | |
130 } | |
131 | |
132 private: | |
133 MediaLoadedEventListener() : EventListener(CPPEventListenerType) {} | |
134 | |
135 void handleEvent(ExecutionContext* context, Event* event) override { | |
136 HTMLVideoElement* media = | |
137 static_cast<HTMLVideoElement*>(event->target()->toNode()); | |
138 UserGestureIndicator gesture( | |
139 DocumentUserGestureToken::create(&media->document())); | |
140 // TODO(shaktisahu): Enable fullscreen after https://crbug/698353 is fixed. | |
141 media->play(); | |
142 } | |
143 }; | |
144 | |
145 void MediaDocumentParser::createDocumentStructure() { | |
146 DCHECK(document()); | |
147 HTMLHtmlElement* rootElement = HTMLHtmlElement::create(*document()); | |
148 document()->appendChild(rootElement); | |
149 rootElement->insertedByParser(); | |
150 | |
151 if (isDetached()) | |
152 return; // runScriptsAtDocumentElementAvailable can detach the frame. | |
153 | |
154 HTMLHeadElement* head = HTMLHeadElement::create(*document()); | |
155 HTMLMetaElement* meta = HTMLMetaElement::create(*document()); | |
156 meta->setAttribute(nameAttr, "viewport"); | |
157 meta->setAttribute(contentAttr, "width=device-width"); | |
158 head->appendChild(meta); | |
159 | |
160 HTMLVideoElement* media = HTMLVideoElement::create(*document()); | |
161 media->setAttribute(controlsAttr, ""); | |
162 media->setAttribute(autoplayAttr, ""); | |
163 media->setAttribute(nameAttr, "media"); | |
164 | |
165 HTMLSourceElement* source = HTMLSourceElement::create(*document()); | |
166 source->setSrc(document()->url()); | |
167 | |
168 if (DocumentLoader* loader = document()->loader()) | |
169 source->setType(loader->responseMIMEType()); | |
170 | |
171 media->appendChild(source); | |
172 | |
173 HTMLBodyElement* body = HTMLBodyElement::create(*document()); | |
174 body->setAttribute(styleAttr, "margin: 0px;"); | |
175 | |
176 document()->willInsertBody(); | |
177 | |
178 HTMLDivElement* div = HTMLDivElement::create(*document()); | |
179 // Style sheets for media controls are lazily loaded until a media element is | |
180 // encountered. As a result, elements encountered before the media element | |
181 // will not get the right style at first if we put the styles in | |
182 // mediacontrols.css. To solve this issue, set the styles inline so that they | |
183 // will be applied when the page loads. See w3c example on how to centering | |
184 // an element: https://www.w3.org/Style/Examples/007/center.en.html | |
185 div->setAttribute(styleAttr, | |
186 "display: flex;" | |
187 "flex-direction: column;" | |
188 "justify-content: center;" | |
189 "align-items: center;" | |
190 "min-height: min-content;" | |
191 "height: 100%;"); | |
192 HTMLContentElement* content = HTMLContentElement::create(*document()); | |
193 div->appendChild(content); | |
194 | |
195 if (document()->settings() && | |
196 document()->settings()->getEmbeddedMediaExperienceEnabled() && | |
197 source->type().startsWith("video/", TextCaseASCIIInsensitive)) { | |
198 EventListener* listener = MediaLoadedEventListener::create(); | |
199 AddEventListenerOptions options; | |
200 options.setOnce(true); | |
201 AddEventListenerOptionsOrBoolean optionsOrBoolean; | |
202 optionsOrBoolean.setAddEventListenerOptions(options); | |
203 media->addEventListener(EventTypeNames::loadedmetadata, listener, | |
204 optionsOrBoolean); | |
205 } | |
206 | |
207 if (RuntimeEnabledFeatures::mediaDocumentDownloadButtonEnabled()) { | |
208 HTMLAnchorElement* anchor = HTMLAnchorElement::create(*document()); | |
209 anchor->setAttribute(downloadAttr, ""); | |
210 anchor->setURL(document()->url()); | |
211 anchor->setTextContent( | |
212 document() | |
213 ->getCachedLocale(document()->contentLanguage()) | |
214 .queryString(WebLocalizedString::DownloadButtonLabel) | |
215 .upper()); | |
216 // Using CSS style according to Android material design. | |
217 anchor->setAttribute( | |
218 styleAttr, | |
219 "display: inline-block;" | |
220 "margin-top: 32px;" | |
221 "padding: 0 16px 0 16px;" | |
222 "height: 36px;" | |
223 "background: #000000;" | |
224 "-webkit-tap-highlight-color: rgba(255, 255, 255, 0.12);" | |
225 "font-family: Roboto;" | |
226 "font-size: 14px;" | |
227 "border-radius: 5px;" | |
228 "color: white;" | |
229 "font-weight: 500;" | |
230 "text-decoration: none;" | |
231 "line-height: 36px;"); | |
232 EventListener* listener = MediaDownloadEventListener::create(); | |
233 anchor->addEventListener(EventTypeNames::click, listener, false); | |
234 HTMLDivElement* buttonContainer = HTMLDivElement::create(*document()); | |
235 buttonContainer->setAttribute(styleAttr, | |
236 "text-align: center;" | |
237 "height: 0;" | |
238 "flex: none"); | |
239 buttonContainer->appendChild(anchor); | |
240 div->appendChild(buttonContainer); | |
241 recordDownloadMetric(MediaDocumentDownloadButtonShown); | |
242 } | |
243 | |
244 // According to | |
245 // https://html.spec.whatwg.org/multipage/browsers.html#read-media, | |
246 // MediaDocument should have a single child which is the video element. Use | |
247 // shadow root to hide all the elements we added here. | |
248 ShadowRoot& shadowRoot = body->ensureUserAgentShadowRoot(); | |
249 shadowRoot.appendChild(div); | |
250 body->appendChild(media); | |
251 rootElement->appendChild(head); | |
252 rootElement->appendChild(body); | |
253 | |
254 m_didBuildDocumentStructure = true; | |
255 } | |
256 | |
257 void MediaDocumentParser::appendBytes(const char*, size_t) { | |
258 if (m_didBuildDocumentStructure) | |
259 return; | |
260 | |
261 createDocumentStructure(); | |
262 finish(); | |
263 } | |
264 | |
265 MediaDocument::MediaDocument(const DocumentInit& initializer) | |
266 : HTMLDocument(initializer, MediaDocumentClass) { | |
267 setCompatibilityMode(QuirksMode); | |
268 lockCompatibilityMode(); | |
269 UseCounter::count(*this, UseCounter::MediaDocument); | |
270 if (!isInMainFrame()) | |
271 UseCounter::count(*this, UseCounter::MediaDocumentInFrame); | |
272 } | |
273 | |
274 DocumentParser* MediaDocument::createParser() { | |
275 return MediaDocumentParser::create(this); | |
276 } | |
277 | |
278 void MediaDocument::defaultEventHandler(Event* event) { | |
279 Node* targetNode = event->target()->toNode(); | |
280 if (!targetNode) | |
281 return; | |
282 | |
283 if (event->type() == EventTypeNames::keydown && event->isKeyboardEvent()) { | |
284 HTMLVideoElement* video = | |
285 Traversal<HTMLVideoElement>::firstWithin(*targetNode); | |
286 if (!video) | |
287 return; | |
288 | |
289 KeyboardEvent* keyboardEvent = toKeyboardEvent(event); | |
290 if (keyboardEvent->key() == " " || | |
291 keyboardEvent->keyCode() == VKEY_MEDIA_PLAY_PAUSE) { | |
292 // space or media key (play/pause) | |
293 video->togglePlayState(); | |
294 event->setDefaultHandled(); | |
295 } | |
296 } | |
297 } | |
298 | |
299 } // namespace blink | |
OLD | NEW |