2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * (C) 2006 Alexey Proskuryakov (ap@webkit.org)
6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All rights reserved.
7 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
8 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
9 * Copyright (C) 2013 Google Inc. All rights reserved.
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Library General Public
13 * License as published by the Free Software Foundation; either
14 * version 2 of the License, or (at your option) any later version.
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Library General Public License for more details.
21 * You should have received a copy of the GNU Library General Public License
22 * along with this library; see the file COPYING.LIB. If not, write to
23 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 * Boston, MA 02110-1301, USA.
29 #include "core/dom/Fullscreen.h"
31 #include "core/HTMLNames.h"
32 #include "core/dom/Document.h"
33 #include "core/events/Event.h"
34 #include "core/frame/FrameHost.h"
35 #include "core/frame/LocalFrame.h"
36 #include "core/frame/Settings.h"
37 #include "core/frame/UseCounter.h"
38 #include "core/html/HTMLIFrameElement.h"
39 #include "core/html/HTMLMediaElement.h"
40 #include "core/page/Chrome.h"
41 #include "core/page/ChromeClient.h"
42 #include "core/page/EventHandler.h"
43 #include "core/rendering/RenderFullScreen.h"
44 #include "platform/UserGestureIndicator.h"
48 using namespace HTMLNames;
50 static bool fullscreenIsAllowedForAllOwners(const Document& document)
52 for (const Element* owner = document.ownerElement(); owner; owner = owner->document().ownerElement()) {
53 if (!isHTMLIFrameElement(owner))
55 if (!owner->hasAttribute(allowfullscreenAttr))
61 static bool fullscreenIsSupported(const Document& document)
63 // Fullscreen is supported if there is no previously-established user preference,
64 // security risk, or platform limitation.
65 return !document.settings() || document.settings()->fullscreenSupported();
68 static bool fullscreenIsSupported(const Document& document, const Element& element)
70 if (!document.settings() || (document.settings()->disallowFullscreenForNonMediaElements() && !isHTMLMediaElement(element)))
72 return fullscreenIsSupported(document);
75 static bool fullscreenElementReady(const Element& element, Fullscreen::RequestType requestType)
77 // A fullscreen element ready check for an element |element| returns true if all of the
78 // following are true, and false otherwise:
80 // |element| is in a document.
81 if (!element.inDocument())
84 // |element|'s node document's fullscreen enabled flag is set.
85 if (!fullscreenIsAllowedForAllOwners(element.document())) {
86 if (requestType == Fullscreen::PrefixedVideoRequest)
87 UseCounter::count(element.document(), UseCounter::VideoFullscreenAllowedExemption);
92 // |element|'s node document's fullscreen element stack is either empty or its top element is an
93 // inclusive ancestor of |element|.
94 if (const Element* topElement = Fullscreen::fullscreenElementFrom(element.document())) {
95 if (!topElement->contains(&element))
99 // |element| has no ancestor element whose local name is iframe and namespace is the HTML
101 if (Traversal<HTMLIFrameElement>::firstAncestor(element))
104 // |element|'s node document's browsing context either has a browsing context container and the
105 // fullscreen element ready check returns true for |element|'s node document's browsing
106 // context's browsing context container, or it has no browsing context container.
107 if (const Element* owner = element.document().ownerElement()) {
108 if (!fullscreenElementReady(*owner, requestType))
115 static bool isPrefixed(const AtomicString& type)
117 return type == EventTypeNames::webkitfullscreenchange || type == EventTypeNames::webkitfullscreenerror;
120 static PassRefPtrWillBeRawPtr<Event> createEvent(const AtomicString& type, EventTarget& target)
122 EventInit initializer;
123 initializer.bubbles = isPrefixed(type);
124 RefPtrWillBeRawPtr<Event> event = Event::create(type, initializer);
125 event->setTarget(&target);
129 const char* Fullscreen::supplementName()
134 Fullscreen& Fullscreen::from(Document& document)
136 Fullscreen* fullscreen = fromIfExists(document);
138 fullscreen = new Fullscreen(document);
139 DocumentSupplement::provideTo(document, supplementName(), adoptPtrWillBeNoop(fullscreen));
145 Fullscreen* Fullscreen::fromIfExistsSlow(Document& document)
147 return static_cast<Fullscreen*>(DocumentSupplement::from(document, supplementName()));
150 Element* Fullscreen::fullscreenElementFrom(Document& document)
152 if (Fullscreen* found = fromIfExists(document))
153 return found->fullscreenElement();
157 Element* Fullscreen::currentFullScreenElementFrom(Document& document)
159 if (Fullscreen* found = fromIfExists(document))
160 return found->webkitCurrentFullScreenElement();
164 bool Fullscreen::isFullScreen(Document& document)
166 return currentFullScreenElementFrom(document);
169 Fullscreen::Fullscreen(Document& document)
170 : DocumentLifecycleObserver(&document)
171 , m_areKeysEnabledInFullScreen(false)
172 , m_fullScreenRenderer(nullptr)
173 , m_eventQueueTimer(this, &Fullscreen::eventQueueTimerFired)
175 document.setHasFullscreenSupplement();
178 Fullscreen::~Fullscreen()
182 inline Document* Fullscreen::document()
184 return lifecycleContext();
187 void Fullscreen::documentWasDetached()
189 m_eventQueue.clear();
191 if (m_fullScreenRenderer)
192 m_fullScreenRenderer->destroy();
195 m_fullScreenElement = nullptr;
196 m_fullScreenElementStack.clear();
202 void Fullscreen::documentWasDisposed()
204 // NOTE: the context dispose phase is not supported in oilpan. Please
205 // consider using the detach phase instead.
206 m_fullScreenElement = nullptr;
207 m_fullScreenElementStack.clear();
211 void Fullscreen::requestFullscreen(Element& element, RequestType requestType)
213 // Ignore this request if the document is not in a live frame.
214 if (!document()->isActive())
217 // If |element| is on top of |doc|'s fullscreen element stack, terminate these substeps.
218 if (&element == fullscreenElement())
222 // 1. If any of the following conditions are true, terminate these steps and queue a task to fire
223 // an event named fullscreenerror with its bubbles attribute set to true on the context object's
226 // The fullscreen element ready check returns false.
227 if (!fullscreenElementReady(element, requestType))
230 // This algorithm is not allowed to show a pop-up:
231 // An algorithm is allowed to show a pop-up if, in the task in which the algorithm is running, either:
232 // - an activation behavior is currently being processed whose click event was trusted, or
233 // - the event listener for a trusted click event is being handled.
234 if (!UserGestureIndicator::processingUserGesture())
237 // Fullscreen is not supported.
238 if (!fullscreenIsSupported(element.document(), element))
241 // 2. Let doc be element's node document. (i.e. "this")
242 Document* currentDoc = document();
244 // 3. Let docs be all doc's ancestor browsing context's documents (if any) and doc.
245 Deque<Document*> docs;
248 docs.prepend(currentDoc);
249 currentDoc = currentDoc->ownerElement() ? ¤tDoc->ownerElement()->document() : 0;
250 } while (currentDoc);
252 // 4. For each document in docs, run these substeps:
253 Deque<Document*>::iterator current = docs.begin(), following = docs.begin();
258 // 1. Let following document be the document after document in docs, or null if there is no
260 Document* currentDoc = *current;
261 Document* followingDoc = following != docs.end() ? *following : 0;
263 // 2. If following document is null, push context object on document's fullscreen element
264 // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
265 // set to true on the document.
267 from(*currentDoc).pushFullscreenElementStack(element, requestType);
268 enqueueChangeEvent(*currentDoc, requestType);
272 // 3. Otherwise, if document's fullscreen element stack is either empty or its top element
273 // is not following document's browsing context container,
274 Element* topElement = fullscreenElementFrom(*currentDoc);
275 if (!topElement || topElement != followingDoc->ownerElement()) {
276 // ...push following document's browsing context container on document's fullscreen element
277 // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
278 // set to true on document.
279 from(*currentDoc).pushFullscreenElementStack(*followingDoc->ownerElement(), requestType);
280 enqueueChangeEvent(*currentDoc, requestType);
284 // 4. Otherwise, do nothing for this document. It stays the same.
285 } while (++current != docs.end());
287 // 5. Return, and run the remaining steps asynchronously.
288 // 6. Optionally, perform some animation.
289 m_areKeysEnabledInFullScreen = requestType != PrefixedMozillaRequest && requestType != PrefixedVideoRequest;
290 document()->frameHost()->chrome().client().enterFullScreenForElement(&element);
292 // 7. Optionally, display a message indicating how the user can exit displaying the context object fullscreen.
296 enqueueErrorEvent(element, requestType);
299 void Fullscreen::fullyExitFullscreen(Document& document)
301 // To fully exit fullscreen, run these steps:
303 // 1. Let |doc| be the top-level browsing context's document.
304 Document& doc = document.topDocument();
306 // 2. If |doc|'s fullscreen element stack is empty, terminate these steps.
307 if (!fullscreenElementFrom(doc))
310 // 3. Remove elements from |doc|'s fullscreen element stack until only the top element is left.
311 size_t stackSize = from(doc).m_fullScreenElementStack.size();
312 from(doc).m_fullScreenElementStack.remove(0, stackSize - 1);
313 ASSERT(from(doc).m_fullScreenElementStack.size() == 1);
315 // 4. Act as if the exitFullscreen() method was invoked on |doc|.
316 from(doc).exitFullscreen();
319 void Fullscreen::exitFullscreen()
321 // The exitFullscreen() method must run these steps:
323 // 1. Let doc be the context object. (i.e. "this")
324 Document* currentDoc = document();
325 if (!currentDoc->isActive())
328 // 2. If doc's fullscreen element stack is empty, terminate these steps.
329 if (m_fullScreenElementStack.isEmpty())
332 // 3. Let descendants be all the doc's descendant browsing context's documents with a non-empty fullscreen
333 // element stack (if any), ordered so that the child of the doc is last and the document furthest
334 // away from the doc is first.
335 WillBeHeapDeque<RefPtrWillBeMember<Document> > descendants;
336 for (Frame* descendant = document()->frame() ? document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext()) {
337 if (!descendant->isLocalFrame())
339 ASSERT(toLocalFrame(descendant)->document());
340 if (fullscreenElementFrom(*toLocalFrame(descendant)->document()))
341 descendants.prepend(toLocalFrame(descendant)->document());
344 // 4. For each descendant in descendants, empty descendant's fullscreen element stack, and queue a
345 // task to fire an event named fullscreenchange with its bubbles attribute set to true on descendant.
346 for (WillBeHeapDeque<RefPtrWillBeMember<Document> >::iterator i = descendants.begin(); i != descendants.end(); ++i) {
348 RequestType requestType = from(**i).m_fullScreenElementStack.last().second;
349 from(**i).clearFullscreenElementStack();
350 enqueueChangeEvent(**i, requestType);
353 // 5. While doc is not null, run these substeps:
356 RequestType requestType = from(*currentDoc).m_fullScreenElementStack.last().second;
358 // 1. Pop the top element of doc's fullscreen element stack.
359 from(*currentDoc).popFullscreenElementStack();
361 // If doc's fullscreen element stack is non-empty and the element now at the top is either
362 // not in a document or its node document is not doc, repeat this substep.
363 newTop = fullscreenElementFrom(*currentDoc);
364 if (newTop && (!newTop->inDocument() || newTop->document() != currentDoc))
367 // 2. Queue a task to fire an event named fullscreenchange with its bubbles attribute set to true
369 enqueueChangeEvent(*currentDoc, requestType);
371 // 3. If doc's fullscreen element stack is empty and doc's browsing context has a browsing context
372 // container, set doc to that browsing context container's node document.
373 if (!newTop && currentDoc->ownerElement()) {
374 currentDoc = ¤tDoc->ownerElement()->document();
378 // 4. Otherwise, set doc to null.
382 // 6. Return, and run the remaining steps asynchronously.
383 // 7. Optionally, perform some animation.
385 FrameHost* host = document()->frameHost();
387 // Speculative fix for engaget.com/videos per crbug.com/336239.
388 // FIXME: This check is wrong. We ASSERT(document->isActive()) above
389 // so this should be redundant and should be removed!
393 // Only exit out of full screen window mode if there are no remaining elements in the
394 // full screen stack.
396 host->chrome().client().exitFullScreenForElement(m_fullScreenElement.get());
400 // Otherwise, notify the chrome of the new full screen element.
401 host->chrome().client().enterFullScreenForElement(newTop);
404 bool Fullscreen::fullscreenEnabled(Document& document)
406 // 4. The fullscreenEnabled attribute must return true if the context object has its
407 // fullscreen enabled flag set and fullscreen is supported, and false otherwise.
409 // Top-level browsing contexts are implied to have their allowFullScreen attribute set.
410 return fullscreenIsAllowedForAllOwners(document) && fullscreenIsSupported(document);
413 void Fullscreen::didEnterFullScreenForElement(Element* element)
416 if (!document()->isActive())
419 if (m_fullScreenRenderer)
420 m_fullScreenRenderer->unwrapRenderer();
422 m_fullScreenElement = element;
424 // Create a placeholder block for a the full-screen element, to keep the page from reflowing
425 // when the element is removed from the normal flow. Only do this for a RenderBox, as only
426 // a box will have a frameRect. The placeholder will be created in setFullScreenRenderer()
428 RenderObject* renderer = m_fullScreenElement->renderer();
429 bool shouldCreatePlaceholder = renderer && renderer->isBox();
430 if (shouldCreatePlaceholder) {
431 m_savedPlaceholderFrameRect = toRenderBox(renderer)->frameRect();
432 m_savedPlaceholderRenderStyle = RenderStyle::clone(renderer->style());
435 if (m_fullScreenElement != document()->documentElement())
436 RenderFullScreen::wrapRenderer(renderer, renderer ? renderer->parent() : 0, document());
438 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
440 // FIXME: This should not call updateStyleIfNeeded.
441 document()->setNeedsStyleRecalc(SubtreeStyleChange);
442 document()->updateRenderTreeIfNeeded();
444 m_fullScreenElement->didBecomeFullscreenElement();
446 if (document()->frame())
447 document()->frame()->eventHandler().scheduleHoverStateUpdate();
449 m_eventQueueTimer.startOneShot(0, FROM_HERE);
452 void Fullscreen::didExitFullScreenForElement(Element*)
454 if (!m_fullScreenElement)
457 if (!document()->isActive())
460 m_fullScreenElement->willStopBeingFullscreenElement();
462 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
464 m_areKeysEnabledInFullScreen = false;
466 if (m_fullScreenRenderer)
467 m_fullScreenRenderer->unwrapRenderer();
469 m_fullScreenElement = nullptr;
470 document()->setNeedsStyleRecalc(SubtreeStyleChange);
472 if (document()->frame())
473 document()->frame()->eventHandler().scheduleHoverStateUpdate();
475 // When fullyExitFullscreen is called, we call exitFullscreen on the topDocument(). That means
476 // that the events will be queued there. So if we have no events here, start the timer on the
478 Document* exitingDocument = document();
479 if (m_eventQueue.isEmpty())
480 exitingDocument = &document()->topDocument();
481 ASSERT(exitingDocument);
482 from(*exitingDocument).m_eventQueueTimer.startOneShot(0, FROM_HERE);
485 void Fullscreen::setFullScreenRenderer(RenderFullScreen* renderer)
487 if (renderer == m_fullScreenRenderer)
490 if (renderer && m_savedPlaceholderRenderStyle) {
491 renderer->createPlaceholder(m_savedPlaceholderRenderStyle.release(), m_savedPlaceholderFrameRect);
492 } else if (renderer && m_fullScreenRenderer && m_fullScreenRenderer->placeholder()) {
493 RenderBlock* placeholder = m_fullScreenRenderer->placeholder();
494 renderer->createPlaceholder(RenderStyle::clone(placeholder->style()), placeholder->frameRect());
497 if (m_fullScreenRenderer)
498 m_fullScreenRenderer->unwrapRenderer();
499 ASSERT(!m_fullScreenRenderer);
501 m_fullScreenRenderer = renderer;
504 void Fullscreen::fullScreenRendererDestroyed()
506 m_fullScreenRenderer = nullptr;
509 void Fullscreen::enqueueChangeEvent(Document& document, RequestType requestType)
511 RefPtrWillBeRawPtr<Event> event;
512 if (requestType == UnprefixedRequest) {
513 event = createEvent(EventTypeNames::fullscreenchange, document);
515 ASSERT(document.hasFullscreenSupplement());
516 Fullscreen& fullscreen = from(document);
517 EventTarget* target = fullscreen.fullscreenElement();
519 target = fullscreen.webkitCurrentFullScreenElement();
522 event = createEvent(EventTypeNames::webkitfullscreenchange, *target);
524 m_eventQueue.append(event);
525 // NOTE: The timer is started in didEnterFullScreenForElement/didExitFullScreenForElement.
528 void Fullscreen::enqueueErrorEvent(Element& element, RequestType requestType)
530 RefPtrWillBeRawPtr<Event> event;
531 if (requestType == UnprefixedRequest)
532 event = createEvent(EventTypeNames::fullscreenerror, element.document());
534 event = createEvent(EventTypeNames::webkitfullscreenerror, element);
535 m_eventQueue.append(event);
536 m_eventQueueTimer.startOneShot(0, FROM_HERE);
539 void Fullscreen::eventQueueTimerFired(Timer<Fullscreen>*)
541 // Since we dispatch events in this function, it's possible that the
542 // document will be detached and GC'd. We protect it here to make sure we
543 // can finish the function successfully.
544 RefPtrWillBeRawPtr<Document> protectDocument(document());
545 WillBeHeapDeque<RefPtrWillBeMember<Event> > eventQueue;
546 m_eventQueue.swap(eventQueue);
548 while (!eventQueue.isEmpty()) {
549 RefPtrWillBeRawPtr<Event> event = eventQueue.takeFirst();
550 Node* target = event->target()->toNode();
552 // If the element was removed from our tree, also message the documentElement.
553 if (!target->inDocument() && document()->documentElement()) {
554 ASSERT(isPrefixed(event->type()));
555 eventQueue.append(createEvent(event->type(), *document()->documentElement()));
558 target->dispatchEvent(event);
562 void Fullscreen::elementRemoved(Element& oldNode)
564 // Whenever the removing steps run with an |oldNode| and |oldNode| is in its node document's
565 // fullscreen element stack, run these steps:
567 // 1. If |oldNode| is at the top of its node document's fullscreen element stack, act as if the
568 // exitFullscreen() method was invoked on that document.
569 if (fullscreenElement() == &oldNode) {
574 // 2. Otherwise, remove |oldNode| from its node document's fullscreen element stack.
575 for (size_t i = 0; i < m_fullScreenElementStack.size(); ++i) {
576 if (m_fullScreenElementStack[i].first.get() == &oldNode) {
577 m_fullScreenElementStack.remove(i);
582 // NOTE: |oldNode| was not in the fullscreen element stack.
585 void Fullscreen::clearFullscreenElementStack()
587 m_fullScreenElementStack.clear();
590 void Fullscreen::popFullscreenElementStack()
592 if (m_fullScreenElementStack.isEmpty())
595 m_fullScreenElementStack.removeLast();
598 void Fullscreen::pushFullscreenElementStack(Element& element, RequestType requestType)
600 m_fullScreenElementStack.append(std::make_pair(&element, requestType));
603 void Fullscreen::trace(Visitor* visitor)
605 visitor->trace(m_fullScreenElement);
606 visitor->trace(m_fullScreenElementStack);
607 visitor->trace(m_fullScreenRenderer);
608 visitor->trace(m_eventQueue);
609 DocumentSupplement::trace(visitor);