ca10505b8206755895de127f128dfe12c1e45d9f
[WebKit-https.git] / Source / WebKit / WebProcess / cocoa / VideoFullscreenManager.mm
1 /*
2  * Copyright (C) 2014-2017 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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 #import "config.h"
26 #import "VideoFullscreenManager.h"
27
28 #if (PLATFORM(IOS) && HAVE(AVKIT)) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
29
30 #import "Attachment.h"
31 #import "Logging.h"
32 #import "PlaybackSessionManager.h"
33 #import "VideoFullscreenManagerMessages.h"
34 #import "VideoFullscreenManagerProxyMessages.h"
35 #import "WebCoreArgumentCoders.h"
36 #import "WebPage.h"
37 #import "WebProcess.h"
38 #import <QuartzCore/CoreAnimation.h>
39 #import <WebCore/Color.h>
40 #import <WebCore/DeprecatedGlobalSettings.h>
41 #import <WebCore/Event.h>
42 #import <WebCore/EventNames.h>
43 #import <WebCore/FrameView.h>
44 #import <WebCore/HTMLVideoElement.h>
45 #import <WebCore/PlatformCALayer.h>
46 #import <WebCore/RenderLayer.h>
47 #import <WebCore/RenderLayerBacking.h>
48 #import <WebCore/RenderVideo.h>
49 #import <WebCore/RenderView.h>
50 #import <WebCore/Settings.h>
51 #import <WebCore/TimeRanges.h>
52 #import <mach/mach_port.h>
53
54 using namespace WebCore;
55
56 namespace WebKit {
57
58 static IntRect inlineVideoFrame(HTMLVideoElement& element)
59 {
60     element.document().updateLayoutIgnorePendingStylesheets();
61     auto* renderer = element.renderer();
62     if (!renderer)
63         return { };
64
65     if (renderer->hasLayer() && renderer->enclosingLayer()->isComposited()) {
66         FloatQuad contentsBox = static_cast<FloatRect>(renderer->enclosingLayer()->backing()->contentsBox());
67         contentsBox = renderer->localToAbsoluteQuad(contentsBox);
68         return element.document().view()->contentsToRootView(contentsBox.enclosingBoundingBox());
69     }
70
71     auto rect = renderer->videoBox();
72     rect.moveBy(renderer->absoluteBoundingBoxRect().location());
73     return element.document().view()->contentsToRootView(rect);
74 }
75
76 #pragma mark - VideoFullscreenInterfaceContext
77
78 VideoFullscreenInterfaceContext::VideoFullscreenInterfaceContext(VideoFullscreenManager& manager, uint64_t contextId)
79     : m_manager(&manager)
80     , m_contextId(contextId)
81 {
82 }
83
84 VideoFullscreenInterfaceContext::~VideoFullscreenInterfaceContext()
85 {
86 }
87
88 void VideoFullscreenInterfaceContext::setLayerHostingContext(std::unique_ptr<LayerHostingContext>&& context)
89 {
90     m_layerHostingContext = WTFMove(context);
91 }
92
93 void VideoFullscreenInterfaceContext::hasVideoChanged(bool hasVideo)
94 {
95     if (m_manager)
96         m_manager->hasVideoChanged(m_contextId, hasVideo);
97 }
98
99 void VideoFullscreenInterfaceContext::videoDimensionsChanged(const FloatSize& videoDimensions)
100 {
101     if (m_manager)
102         m_manager->videoDimensionsChanged(m_contextId, videoDimensions);
103 }
104
105 #pragma mark - VideoFullscreenManager
106
107 Ref<VideoFullscreenManager> VideoFullscreenManager::create(WebPage& page, PlaybackSessionManager& playbackSessionManager)
108 {
109     return adoptRef(*new VideoFullscreenManager(page, playbackSessionManager));
110 }
111
112 VideoFullscreenManager::VideoFullscreenManager(WebPage& page, PlaybackSessionManager& playbackSessionManager)
113     : m_page(&page)
114     , m_playbackSessionManager(playbackSessionManager)
115 {
116     WebProcess::singleton().addMessageReceiver(Messages::VideoFullscreenManager::messageReceiverName(), page.pageID(), *this);
117 }
118
119 VideoFullscreenManager::~VideoFullscreenManager()
120 {
121     for (auto& tuple : m_contextMap.values()) {
122         RefPtr<VideoFullscreenModelVideoElement> model;
123         RefPtr<VideoFullscreenInterfaceContext> interface;
124         std::tie(model, interface) = tuple;
125
126         model->setVideoElement(nullptr);
127         model->removeClient(*interface);
128
129         interface->invalidate();
130     }
131
132     m_contextMap.clear();
133     m_videoElements.clear();
134     m_clientCounts.clear();
135     
136     if (m_page)
137         WebProcess::singleton().removeMessageReceiver(Messages::VideoFullscreenManager::messageReceiverName(), m_page->pageID());
138 }
139
140 void VideoFullscreenManager::invalidate()
141 {
142     ASSERT(m_page);
143     WebProcess::singleton().removeMessageReceiver(Messages::VideoFullscreenManager::messageReceiverName(), m_page->pageID());
144     m_page = nullptr;
145 }
146
147 VideoFullscreenManager::ModelInterfaceTuple VideoFullscreenManager::createModelAndInterface(uint64_t contextId)
148 {
149     RefPtr<VideoFullscreenModelVideoElement> model = VideoFullscreenModelVideoElement::create();
150     RefPtr<VideoFullscreenInterfaceContext> interface = VideoFullscreenInterfaceContext::create(*this, contextId);
151     m_playbackSessionManager->addClientForContext(contextId);
152
153     interface->setLayerHostingContext(LayerHostingContext::createForExternalHostingProcess());
154     model->addClient(*interface);
155
156     return std::make_tuple(WTFMove(model), WTFMove(interface));
157 }
158
159 VideoFullscreenManager::ModelInterfaceTuple& VideoFullscreenManager::ensureModelAndInterface(uint64_t contextId)
160 {
161     auto addResult = m_contextMap.add(contextId, ModelInterfaceTuple());
162     if (addResult.isNewEntry)
163         addResult.iterator->value = createModelAndInterface(contextId);
164     return addResult.iterator->value;
165 }
166
167 WebCore::VideoFullscreenModelVideoElement& VideoFullscreenManager::ensureModel(uint64_t contextId)
168 {
169     return *std::get<0>(ensureModelAndInterface(contextId));
170 }
171
172 VideoFullscreenInterfaceContext& VideoFullscreenManager::ensureInterface(uint64_t contextId)
173 {
174     return *std::get<1>(ensureModelAndInterface(contextId));
175 }
176
177 void VideoFullscreenManager::removeContext(uint64_t contextId)
178 {
179     RefPtr<VideoFullscreenModelVideoElement> model;
180     RefPtr<VideoFullscreenInterfaceContext> interface;
181     std::tie(model, interface) = ensureModelAndInterface(contextId);
182
183     m_playbackSessionManager->removeClientForContext(contextId);
184
185     RefPtr<HTMLVideoElement> videoElement = model->videoElement();
186     model->setVideoElement(nullptr);
187     model->removeClient(*interface);
188     interface->invalidate();
189     m_videoElements.remove(videoElement.get());
190     m_contextMap.remove(contextId);
191 }
192
193 void VideoFullscreenManager::addClientForContext(uint64_t contextId)
194 {
195     auto addResult = m_clientCounts.add(contextId, 1);
196     if (!addResult.isNewEntry)
197         addResult.iterator->value++;
198 }
199
200 void VideoFullscreenManager::removeClientForContext(uint64_t contextId)
201 {
202     ASSERT(m_clientCounts.contains(contextId));
203
204     int clientCount = m_clientCounts.get(contextId);
205     ASSERT(clientCount > 0);
206     clientCount--;
207
208     if (clientCount <= 0) {
209         m_clientCounts.remove(contextId);
210         removeContext(contextId);
211         return;
212     }
213
214     m_clientCounts.set(contextId, clientCount);
215 }
216
217 #pragma mark Interface to ChromeClient:
218
219 bool VideoFullscreenManager::supportsVideoFullscreen(WebCore::HTMLMediaElementEnums::VideoFullscreenMode mode) const
220 {
221 #if PLATFORM(IOS)
222     UNUSED_PARAM(mode);
223     return DeprecatedGlobalSettings::avKitEnabled();
224 #else
225     return mode == HTMLMediaElementEnums::VideoFullscreenModePictureInPicture && supportsPictureInPicture();
226 #endif
227 }
228
229 void VideoFullscreenManager::enterVideoFullscreenForVideoElement(HTMLVideoElement& videoElement, HTMLMediaElementEnums::VideoFullscreenMode mode)
230 {
231     ASSERT(m_page);
232     ASSERT(mode != HTMLMediaElementEnums::VideoFullscreenModeNone);
233     LOG(Fullscreen, "VideoFullscreenManager::enterVideoFullscreenForVideoElement(%p)", this);
234
235     uint64_t contextId = m_playbackSessionManager->contextIdForMediaElement(videoElement);
236     auto addResult = m_videoElements.add(&videoElement, contextId);
237     UNUSED_PARAM(addResult);
238     ASSERT(addResult.iterator->value == contextId);
239
240     RefPtr<VideoFullscreenModelVideoElement> model;
241     RefPtr<VideoFullscreenInterfaceContext> interface;
242     std::tie(model, interface) = ensureModelAndInterface(contextId);
243     addClientForContext(contextId);
244     if (!interface->layerHostingContext())
245         interface->setLayerHostingContext(LayerHostingContext::createForExternalHostingProcess());
246
247     auto videoRect = inlineVideoFrame(videoElement);
248     FloatRect videoLayerFrame = FloatRect(0, 0, videoRect.width(), videoRect.height());
249
250     HTMLMediaElementEnums::VideoFullscreenMode oldMode = interface->fullscreenMode();
251     interface->setTargetIsFullscreen(true);
252     interface->setFullscreenMode(mode);
253     model->setVideoElement(&videoElement);
254     if (oldMode == HTMLMediaElementEnums::VideoFullscreenModeNone)
255         model->setVideoLayerFrame(videoLayerFrame);
256
257     if (interface->isAnimating())
258         return;
259     interface->setIsAnimating(true);
260
261     bool allowsPictureInPicture = videoElement.webkitSupportsPresentationMode(HTMLVideoElement::VideoPresentationMode::PictureInPicture);
262     
263     m_page->send(Messages::VideoFullscreenManagerProxy::SetupFullscreenWithID(contextId, interface->layerHostingContext()->contextID(), videoRect, m_page->deviceScaleFactor(), interface->fullscreenMode(), allowsPictureInPicture), m_page->pageID());
264 }
265
266 void VideoFullscreenManager::exitVideoFullscreenForVideoElement(WebCore::HTMLVideoElement& videoElement)
267 {
268     LOG(Fullscreen, "VideoFullscreenManager::exitVideoFullscreenForVideoElement(%p)", this);
269     ASSERT(m_page);
270     ASSERT(m_videoElements.contains(&videoElement));
271
272     uint64_t contextId = m_videoElements.get(&videoElement);
273     auto& interface = ensureInterface(contextId);
274
275     interface.setTargetIsFullscreen(false);
276
277     if (interface.isAnimating())
278         return;
279
280     interface.setIsAnimating(true);
281     m_page->send(Messages::VideoFullscreenManagerProxy::ExitFullscreen(contextId, inlineVideoFrame(videoElement)), m_page->pageID());
282 }
283
284 void VideoFullscreenManager::exitVideoFullscreenToModeWithoutAnimation(WebCore::HTMLVideoElement& videoElement, WebCore::HTMLMediaElementEnums::VideoFullscreenMode targetMode)
285 {
286     LOG(Fullscreen, "VideoFullscreenManager::exitVideoFullscreenToModeWithoutAnimation(%p)", this);
287
288 #if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
289     ASSERT(m_page);
290     ASSERT(m_videoElements.contains(&videoElement));
291
292     uint64_t contextId = m_videoElements.get(&videoElement);
293     auto& interface = ensureInterface(contextId);
294
295     interface.setTargetIsFullscreen(false);
296
297     m_page->send(Messages::VideoFullscreenManagerProxy::ExitFullscreenWithoutAnimationToMode(contextId, targetMode), m_page->pageID());
298 #else
299     UNUSED_PARAM(videoElement);
300     UNUSED_PARAM(targetMode);
301 #endif
302 }
303
304 #pragma mark Interface to VideoFullscreenInterfaceContext:
305
306 void VideoFullscreenManager::hasVideoChanged(uint64_t contextId, bool hasVideo)
307 {
308     if (m_page)
309         m_page->send(Messages::VideoFullscreenManagerProxy::SetHasVideo(contextId, hasVideo), m_page->pageID());
310 }
311
312 void VideoFullscreenManager::videoDimensionsChanged(uint64_t contextId, const FloatSize& videoDimensions)
313 {
314     if (m_page)
315         m_page->send(Messages::VideoFullscreenManagerProxy::SetVideoDimensions(contextId, videoDimensions), m_page->pageID());
316 }
317
318 #pragma mark Messages from VideoFullscreenManagerProxy:
319
320 void VideoFullscreenManager::requestFullscreenMode(uint64_t contextId, WebCore::HTMLMediaElementEnums::VideoFullscreenMode mode, bool finishedWithMedia)
321 {
322     ensureModel(contextId).requestFullscreenMode(mode, finishedWithMedia);
323 }
324
325 void VideoFullscreenManager::fullscreenModeChanged(uint64_t contextId, WebCore::HTMLMediaElementEnums::VideoFullscreenMode videoFullscreenMode)
326 {
327     ensureModel(contextId).fullscreenModeChanged(videoFullscreenMode);
328 }
329
330 void VideoFullscreenManager::didSetupFullscreen(uint64_t contextId)
331 {
332     LOG(Fullscreen, "VideoFullscreenManager::didSetupFullscreen(%p, %x)", this, contextId);
333
334     ASSERT(m_page);
335     PlatformLayer* videoLayer = [CALayer layer];
336 #ifndef NDEBUG
337     [videoLayer setName:@"Web video fullscreen manager layer"];
338 #endif
339
340     [CATransaction begin];
341     [CATransaction setDisableActions:YES];
342
343     [videoLayer setPosition:CGPointMake(0, 0)];
344     [videoLayer setBackgroundColor:cachedCGColor(WebCore::Color::transparent)];
345
346     // Set a scale factor here to make convertRect:toLayer:nil take scale factor into account. <rdar://problem/18316542>.
347     // This scale factor is inverted in the hosting process.
348     float hostingScaleFactor = m_page->deviceScaleFactor();
349     [videoLayer setTransform:CATransform3DMakeScale(hostingScaleFactor, hostingScaleFactor, 1)];
350
351     RefPtr<VideoFullscreenModelVideoElement> model;
352     RefPtr<VideoFullscreenInterfaceContext> interface;
353     std::tie(model, interface) = ensureModelAndInterface(contextId);
354
355     interface->layerHostingContext()->setRootLayer(videoLayer);
356
357     RefPtr<VideoFullscreenManager> protectedThis(this);
358     
359     model->setVideoFullscreenLayer(videoLayer, [protectedThis, this, contextId] {
360         dispatch_async(dispatch_get_main_queue(), [protectedThis, this, contextId] {
361             if (protectedThis->m_page)
362                 m_page->send(Messages::VideoFullscreenManagerProxy::EnterFullscreen(contextId), protectedThis->m_page->pageID());
363         });
364     });
365     
366     [CATransaction commit];
367 }
368     
369 void VideoFullscreenManager::didEnterFullscreen(uint64_t contextId)
370 {
371     LOG(Fullscreen, "VideoFullscreenManager::didEnterFullscreen(%p, %x)", this, contextId);
372
373     RefPtr<VideoFullscreenModelVideoElement> model;
374     RefPtr<VideoFullscreenInterfaceContext> interface;
375     std::tie(model, interface) = ensureModelAndInterface(contextId);
376
377     interface->setIsAnimating(false);
378     interface->setIsFullscreen(false);
379
380     if (interface->targetIsFullscreen())
381         return;
382
383     RefPtr<HTMLVideoElement> videoElement = model->videoElement();
384     if (!videoElement)
385         return;
386
387     // exit fullscreen now if it was previously requested during an animation.
388     RefPtr<VideoFullscreenManager> protectedThis(this);
389     dispatch_async(dispatch_get_main_queue(), [protectedThis, videoElement] {
390         if (protectedThis->m_page)
391             protectedThis->exitVideoFullscreenForVideoElement(*videoElement);
392     });
393 }
394
395 void VideoFullscreenManager::didExitFullscreen(uint64_t contextId)
396 {
397     LOG(Fullscreen, "VideoFullscreenManager::didExitFullscreen(%p, %x)", this, contextId);
398
399     RefPtr<VideoFullscreenModelVideoElement> model;
400     RefPtr<VideoFullscreenInterfaceContext> interface;
401     std::tie(model, interface) = ensureModelAndInterface(contextId);
402     RefPtr<VideoFullscreenManager> protectedThis(this);
403     
404     model->waitForPreparedForInlineThen([protectedThis, contextId, interface, model] {
405         dispatch_async(dispatch_get_main_queue(), [protectedThis, contextId, interface, model] {
406             model->setVideoFullscreenLayer(nil, [protectedThis, contextId, interface] {
407                 dispatch_async(dispatch_get_main_queue(), [protectedThis, contextId, interface] {
408                     if (interface->layerHostingContext()) {
409                         interface->layerHostingContext()->setRootLayer(nullptr);
410                         interface->setLayerHostingContext(nullptr);
411                     }
412                     if (protectedThis->m_page)
413                         protectedThis->m_page->send(Messages::VideoFullscreenManagerProxy::CleanupFullscreen(contextId), protectedThis->m_page->pageID());
414                 });
415             });
416         });
417     });
418 }
419     
420 void VideoFullscreenManager::didCleanupFullscreen(uint64_t contextId)
421 {
422     LOG(Fullscreen, "VideoFullscreenManager::didCleanupFullscreen(%p, %x)", this, contextId);
423
424     RefPtr<VideoFullscreenModelVideoElement> model;
425     RefPtr<VideoFullscreenInterfaceContext> interface;
426     std::tie(model, interface) = ensureModelAndInterface(contextId);
427
428     interface->setIsAnimating(false);
429     interface->setIsFullscreen(false);
430     HTMLMediaElementEnums::VideoFullscreenMode mode = interface->fullscreenMode();
431     bool targetIsFullscreen = interface->targetIsFullscreen();
432
433     model->setVideoFullscreenLayer(nil);
434     RefPtr<HTMLVideoElement> videoElement = model->videoElement();
435
436     interface->setFullscreenMode(HTMLMediaElementEnums::VideoFullscreenModeNone);
437     removeClientForContext(contextId);
438
439     if (!videoElement || !targetIsFullscreen)
440         return;
441
442     RefPtr<VideoFullscreenManager> protectedThis(this);
443     dispatch_async(dispatch_get_main_queue(), [protectedThis, videoElement, mode] {
444         if (protectedThis->m_page)
445             protectedThis->enterVideoFullscreenForVideoElement(*videoElement, mode);
446     });
447 }
448     
449 void VideoFullscreenManager::setVideoLayerGravityEnum(uint64_t contextId, unsigned gravity)
450 {
451     ensureModel(contextId).setVideoLayerGravity((VideoFullscreenModel::VideoGravity)gravity);
452 }
453     
454 void VideoFullscreenManager::fullscreenMayReturnToInline(uint64_t contextId, bool isPageVisible)
455 {
456     if (!m_page)
457         return;
458
459     auto& model = ensureModel(contextId);
460
461     if (!isPageVisible)
462         model.videoElement()->scrollIntoViewIfNotVisible(false);
463     m_page->send(Messages::VideoFullscreenManagerProxy::PreparedToReturnToInline(contextId, true, inlineVideoFrame(*model.videoElement())), m_page->pageID());
464 }
465     
466 void VideoFullscreenManager::setVideoLayerFrameFenced(uint64_t contextId, WebCore::FloatRect bounds, IPC::Attachment fencePort)
467 {
468     LOG(Fullscreen, "VideoFullscreenManager::setVideoLayerFrameFenced(%p, %x)", this, contextId);
469
470     RefPtr<VideoFullscreenModelVideoElement> model;
471     RefPtr<VideoFullscreenInterfaceContext> interface;
472     std::tie(model, interface) = ensureModelAndInterface(contextId);
473
474     if (std::isnan(bounds.x()) || std::isnan(bounds.y()) || std::isnan(bounds.width()) || std::isnan(bounds.height())) {
475         auto videoRect = inlineVideoFrame(*model->videoElement());
476         bounds = FloatRect(0, 0, videoRect.width(), videoRect.height());
477     }
478     
479     [CATransaction begin];
480     [CATransaction setAnimationDuration:0];
481     if (interface->layerHostingContext())
482         interface->layerHostingContext()->setFencePort(fencePort.port());
483     model->setVideoLayerFrame(bounds);
484     mach_port_deallocate(mach_task_self(), fencePort.port());
485     [CATransaction commit];
486 }
487
488 } // namespace WebKit
489
490 #endif // PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))