2 * Copyright (C) 2014-2017 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 #import "VideoFullscreenManager.h"
28 #if (PLATFORM(IOS) && HAVE(AVKIT)) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
30 #import "Attachment.h"
32 #import "PlaybackSessionManager.h"
33 #import "VideoFullscreenManagerMessages.h"
34 #import "VideoFullscreenManagerProxyMessages.h"
35 #import "WebCoreArgumentCoders.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>
54 using namespace WebCore;
58 static IntRect inlineVideoFrame(HTMLVideoElement& element)
60 element.document().updateLayoutIgnorePendingStylesheets();
61 auto* renderer = element.renderer();
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());
71 auto rect = renderer->videoBox();
72 rect.moveBy(renderer->absoluteBoundingBoxRect().location());
73 return element.document().view()->contentsToRootView(rect);
76 #pragma mark - VideoFullscreenInterfaceContext
78 VideoFullscreenInterfaceContext::VideoFullscreenInterfaceContext(VideoFullscreenManager& manager, uint64_t contextId)
80 , m_contextId(contextId)
84 VideoFullscreenInterfaceContext::~VideoFullscreenInterfaceContext()
88 void VideoFullscreenInterfaceContext::setLayerHostingContext(std::unique_ptr<LayerHostingContext>&& context)
90 m_layerHostingContext = WTFMove(context);
93 void VideoFullscreenInterfaceContext::hasVideoChanged(bool hasVideo)
96 m_manager->hasVideoChanged(m_contextId, hasVideo);
99 void VideoFullscreenInterfaceContext::videoDimensionsChanged(const FloatSize& videoDimensions)
102 m_manager->videoDimensionsChanged(m_contextId, videoDimensions);
105 #pragma mark - VideoFullscreenManager
107 Ref<VideoFullscreenManager> VideoFullscreenManager::create(WebPage& page, PlaybackSessionManager& playbackSessionManager)
109 return adoptRef(*new VideoFullscreenManager(page, playbackSessionManager));
112 VideoFullscreenManager::VideoFullscreenManager(WebPage& page, PlaybackSessionManager& playbackSessionManager)
114 , m_playbackSessionManager(playbackSessionManager)
116 WebProcess::singleton().addMessageReceiver(Messages::VideoFullscreenManager::messageReceiverName(), page.pageID(), *this);
119 VideoFullscreenManager::~VideoFullscreenManager()
121 for (auto& tuple : m_contextMap.values()) {
122 RefPtr<VideoFullscreenModelVideoElement> model;
123 RefPtr<VideoFullscreenInterfaceContext> interface;
124 std::tie(model, interface) = tuple;
126 model->setVideoElement(nullptr);
127 model->removeClient(*interface);
129 interface->invalidate();
132 m_contextMap.clear();
133 m_videoElements.clear();
134 m_clientCounts.clear();
137 WebProcess::singleton().removeMessageReceiver(Messages::VideoFullscreenManager::messageReceiverName(), m_page->pageID());
140 void VideoFullscreenManager::invalidate()
143 WebProcess::singleton().removeMessageReceiver(Messages::VideoFullscreenManager::messageReceiverName(), m_page->pageID());
147 VideoFullscreenManager::ModelInterfaceTuple VideoFullscreenManager::createModelAndInterface(uint64_t contextId)
149 RefPtr<VideoFullscreenModelVideoElement> model = VideoFullscreenModelVideoElement::create();
150 RefPtr<VideoFullscreenInterfaceContext> interface = VideoFullscreenInterfaceContext::create(*this, contextId);
151 m_playbackSessionManager->addClientForContext(contextId);
153 interface->setLayerHostingContext(LayerHostingContext::createForExternalHostingProcess());
154 model->addClient(*interface);
156 return std::make_tuple(WTFMove(model), WTFMove(interface));
159 VideoFullscreenManager::ModelInterfaceTuple& VideoFullscreenManager::ensureModelAndInterface(uint64_t contextId)
161 auto addResult = m_contextMap.add(contextId, ModelInterfaceTuple());
162 if (addResult.isNewEntry)
163 addResult.iterator->value = createModelAndInterface(contextId);
164 return addResult.iterator->value;
167 WebCore::VideoFullscreenModelVideoElement& VideoFullscreenManager::ensureModel(uint64_t contextId)
169 return *std::get<0>(ensureModelAndInterface(contextId));
172 VideoFullscreenInterfaceContext& VideoFullscreenManager::ensureInterface(uint64_t contextId)
174 return *std::get<1>(ensureModelAndInterface(contextId));
177 void VideoFullscreenManager::removeContext(uint64_t contextId)
179 RefPtr<VideoFullscreenModelVideoElement> model;
180 RefPtr<VideoFullscreenInterfaceContext> interface;
181 std::tie(model, interface) = ensureModelAndInterface(contextId);
183 m_playbackSessionManager->removeClientForContext(contextId);
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);
193 void VideoFullscreenManager::addClientForContext(uint64_t contextId)
195 auto addResult = m_clientCounts.add(contextId, 1);
196 if (!addResult.isNewEntry)
197 addResult.iterator->value++;
200 void VideoFullscreenManager::removeClientForContext(uint64_t contextId)
202 ASSERT(m_clientCounts.contains(contextId));
204 int clientCount = m_clientCounts.get(contextId);
205 ASSERT(clientCount > 0);
208 if (clientCount <= 0) {
209 m_clientCounts.remove(contextId);
210 removeContext(contextId);
214 m_clientCounts.set(contextId, clientCount);
217 #pragma mark Interface to ChromeClient:
219 bool VideoFullscreenManager::supportsVideoFullscreen(WebCore::HTMLMediaElementEnums::VideoFullscreenMode mode) const
223 return DeprecatedGlobalSettings::avKitEnabled();
225 return mode == HTMLMediaElementEnums::VideoFullscreenModePictureInPicture && supportsPictureInPicture();
229 void VideoFullscreenManager::enterVideoFullscreenForVideoElement(HTMLVideoElement& videoElement, HTMLMediaElementEnums::VideoFullscreenMode mode)
232 ASSERT(mode != HTMLMediaElementEnums::VideoFullscreenModeNone);
233 LOG(Fullscreen, "VideoFullscreenManager::enterVideoFullscreenForVideoElement(%p)", this);
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);
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());
247 auto videoRect = inlineVideoFrame(videoElement);
248 FloatRect videoLayerFrame = FloatRect(0, 0, videoRect.width(), videoRect.height());
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);
257 if (interface->isAnimating())
259 interface->setIsAnimating(true);
261 bool allowsPictureInPicture = videoElement.webkitSupportsPresentationMode(HTMLVideoElement::VideoPresentationMode::PictureInPicture);
263 m_page->send(Messages::VideoFullscreenManagerProxy::SetupFullscreenWithID(contextId, interface->layerHostingContext()->contextID(), videoRect, m_page->deviceScaleFactor(), interface->fullscreenMode(), allowsPictureInPicture), m_page->pageID());
266 void VideoFullscreenManager::exitVideoFullscreenForVideoElement(WebCore::HTMLVideoElement& videoElement)
268 LOG(Fullscreen, "VideoFullscreenManager::exitVideoFullscreenForVideoElement(%p)", this);
270 ASSERT(m_videoElements.contains(&videoElement));
272 uint64_t contextId = m_videoElements.get(&videoElement);
273 auto& interface = ensureInterface(contextId);
275 interface.setTargetIsFullscreen(false);
277 if (interface.isAnimating())
280 interface.setIsAnimating(true);
281 m_page->send(Messages::VideoFullscreenManagerProxy::ExitFullscreen(contextId, inlineVideoFrame(videoElement)), m_page->pageID());
284 void VideoFullscreenManager::exitVideoFullscreenToModeWithoutAnimation(WebCore::HTMLVideoElement& videoElement, WebCore::HTMLMediaElementEnums::VideoFullscreenMode targetMode)
286 LOG(Fullscreen, "VideoFullscreenManager::exitVideoFullscreenToModeWithoutAnimation(%p)", this);
288 #if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
290 ASSERT(m_videoElements.contains(&videoElement));
292 uint64_t contextId = m_videoElements.get(&videoElement);
293 auto& interface = ensureInterface(contextId);
295 interface.setTargetIsFullscreen(false);
297 m_page->send(Messages::VideoFullscreenManagerProxy::ExitFullscreenWithoutAnimationToMode(contextId, targetMode), m_page->pageID());
299 UNUSED_PARAM(videoElement);
300 UNUSED_PARAM(targetMode);
304 #pragma mark Interface to VideoFullscreenInterfaceContext:
306 void VideoFullscreenManager::hasVideoChanged(uint64_t contextId, bool hasVideo)
309 m_page->send(Messages::VideoFullscreenManagerProxy::SetHasVideo(contextId, hasVideo), m_page->pageID());
312 void VideoFullscreenManager::videoDimensionsChanged(uint64_t contextId, const FloatSize& videoDimensions)
315 m_page->send(Messages::VideoFullscreenManagerProxy::SetVideoDimensions(contextId, videoDimensions), m_page->pageID());
318 #pragma mark Messages from VideoFullscreenManagerProxy:
320 void VideoFullscreenManager::requestFullscreenMode(uint64_t contextId, WebCore::HTMLMediaElementEnums::VideoFullscreenMode mode, bool finishedWithMedia)
322 ensureModel(contextId).requestFullscreenMode(mode, finishedWithMedia);
325 void VideoFullscreenManager::fullscreenModeChanged(uint64_t contextId, WebCore::HTMLMediaElementEnums::VideoFullscreenMode videoFullscreenMode)
327 ensureModel(contextId).fullscreenModeChanged(videoFullscreenMode);
330 void VideoFullscreenManager::didSetupFullscreen(uint64_t contextId)
332 LOG(Fullscreen, "VideoFullscreenManager::didSetupFullscreen(%p, %x)", this, contextId);
335 PlatformLayer* videoLayer = [CALayer layer];
337 [videoLayer setName:@"Web video fullscreen manager layer"];
340 [CATransaction begin];
341 [CATransaction setDisableActions:YES];
343 [videoLayer setPosition:CGPointMake(0, 0)];
344 [videoLayer setBackgroundColor:cachedCGColor(WebCore::Color::transparent)];
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)];
351 RefPtr<VideoFullscreenModelVideoElement> model;
352 RefPtr<VideoFullscreenInterfaceContext> interface;
353 std::tie(model, interface) = ensureModelAndInterface(contextId);
355 interface->layerHostingContext()->setRootLayer(videoLayer);
357 RefPtr<VideoFullscreenManager> protectedThis(this);
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());
366 [CATransaction commit];
369 void VideoFullscreenManager::didEnterFullscreen(uint64_t contextId)
371 LOG(Fullscreen, "VideoFullscreenManager::didEnterFullscreen(%p, %x)", this, contextId);
373 RefPtr<VideoFullscreenModelVideoElement> model;
374 RefPtr<VideoFullscreenInterfaceContext> interface;
375 std::tie(model, interface) = ensureModelAndInterface(contextId);
377 interface->setIsAnimating(false);
378 interface->setIsFullscreen(false);
380 if (interface->targetIsFullscreen())
383 RefPtr<HTMLVideoElement> videoElement = model->videoElement();
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);
395 void VideoFullscreenManager::didExitFullscreen(uint64_t contextId)
397 LOG(Fullscreen, "VideoFullscreenManager::didExitFullscreen(%p, %x)", this, contextId);
399 RefPtr<VideoFullscreenModelVideoElement> model;
400 RefPtr<VideoFullscreenInterfaceContext> interface;
401 std::tie(model, interface) = ensureModelAndInterface(contextId);
402 RefPtr<VideoFullscreenManager> protectedThis(this);
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);
412 if (protectedThis->m_page)
413 protectedThis->m_page->send(Messages::VideoFullscreenManagerProxy::CleanupFullscreen(contextId), protectedThis->m_page->pageID());
420 void VideoFullscreenManager::didCleanupFullscreen(uint64_t contextId)
422 LOG(Fullscreen, "VideoFullscreenManager::didCleanupFullscreen(%p, %x)", this, contextId);
424 RefPtr<VideoFullscreenModelVideoElement> model;
425 RefPtr<VideoFullscreenInterfaceContext> interface;
426 std::tie(model, interface) = ensureModelAndInterface(contextId);
428 interface->setIsAnimating(false);
429 interface->setIsFullscreen(false);
430 HTMLMediaElementEnums::VideoFullscreenMode mode = interface->fullscreenMode();
431 bool targetIsFullscreen = interface->targetIsFullscreen();
433 model->setVideoFullscreenLayer(nil);
434 RefPtr<HTMLVideoElement> videoElement = model->videoElement();
436 interface->setFullscreenMode(HTMLMediaElementEnums::VideoFullscreenModeNone);
437 removeClientForContext(contextId);
439 if (!videoElement || !targetIsFullscreen)
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);
449 void VideoFullscreenManager::setVideoLayerGravityEnum(uint64_t contextId, unsigned gravity)
451 ensureModel(contextId).setVideoLayerGravity((VideoFullscreenModel::VideoGravity)gravity);
454 void VideoFullscreenManager::fullscreenMayReturnToInline(uint64_t contextId, bool isPageVisible)
459 auto& model = ensureModel(contextId);
462 model.videoElement()->scrollIntoViewIfNotVisible(false);
463 m_page->send(Messages::VideoFullscreenManagerProxy::PreparedToReturnToInline(contextId, true, inlineVideoFrame(*model.videoElement())), m_page->pageID());
466 void VideoFullscreenManager::setVideoLayerFrameFenced(uint64_t contextId, WebCore::FloatRect bounds, IPC::Attachment fencePort)
468 LOG(Fullscreen, "VideoFullscreenManager::setVideoLayerFrameFenced(%p, %x)", this, contextId);
470 RefPtr<VideoFullscreenModelVideoElement> model;
471 RefPtr<VideoFullscreenInterfaceContext> interface;
472 std::tie(model, interface) = ensureModelAndInterface(contextId);
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());
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];
488 } // namespace WebKit
490 #endif // PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))