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/FullscreenElementStack.h"
31 #include "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/UseCounter.h"
37 #include "core/html/HTMLFrameOwnerElement.h"
38 #include "core/page/Chrome.h"
39 #include "core/page/ChromeClient.h"
40 #include "core/rendering/RenderFullScreen.h"
41 #include "platform/UserGestureIndicator.h"
45 using namespace HTMLNames;
47 static bool fullscreenIsAllowedForAllOwners(const Document& document)
49 const HTMLFrameOwnerElement* owner = document.ownerElement();
53 if (!owner->hasAttribute(allowfullscreenAttr)) {
54 if (owner->hasAttribute(webkitallowfullscreenAttr))
55 UseCounter::count(document, UseCounter::PrefixedAllowFullscreenAttribute);
59 } while ((owner = owner->document().ownerElement()));
63 const char* FullscreenElementStack::supplementName()
65 return "FullscreenElementStack";
68 FullscreenElementStack& FullscreenElementStack::from(Document& document)
70 FullscreenElementStack* fullscreen = fromIfExists(document);
72 fullscreen = new FullscreenElementStack(document);
73 DocumentSupplement::provideTo(document, supplementName(), adoptPtrWillBeNoop(fullscreen));
79 FullscreenElementStack* FullscreenElementStack::fromIfExistsSlow(Document& document)
81 return static_cast<FullscreenElementStack*>(DocumentSupplement::from(document, supplementName()));
84 Element* FullscreenElementStack::fullscreenElementFrom(Document& document)
86 if (FullscreenElementStack* found = fromIfExists(document))
87 return found->webkitFullscreenElement();
91 Element* FullscreenElementStack::currentFullScreenElementFrom(Document& document)
93 if (FullscreenElementStack* found = fromIfExists(document))
94 return found->webkitCurrentFullScreenElement();
98 bool FullscreenElementStack::isFullScreen(Document& document)
100 if (FullscreenElementStack* found = fromIfExists(document))
101 return found->webkitIsFullScreen();
105 FullscreenElementStack::FullscreenElementStack(Document& document)
106 : DocumentLifecycleObserver(&document)
107 , m_areKeysEnabledInFullScreen(false)
108 , m_fullScreenRenderer(0)
109 , m_fullScreenChangeDelayTimer(this, &FullscreenElementStack::fullScreenChangeDelayTimerFired)
111 document.setHasFullscreenElementStack();
114 FullscreenElementStack::~FullscreenElementStack()
118 inline Document* FullscreenElementStack::document()
120 return lifecycleContext();
123 void FullscreenElementStack::documentWasDetached()
125 m_fullScreenChangeEventTargetQueue.clear();
126 m_fullScreenErrorEventTargetQueue.clear();
128 if (m_fullScreenRenderer)
129 setFullScreenRenderer(0);
132 void FullscreenElementStack::documentWasDisposed()
134 m_fullScreenElement = nullptr;
135 m_fullScreenElementStack.clear();
138 bool FullscreenElementStack::fullScreenIsAllowedForElement(Element* element) const
141 return fullscreenIsAllowedForAllOwners(element->document());
144 void FullscreenElementStack::requestFullScreenForElement(Element* element, unsigned short flags, FullScreenCheckType checkType)
146 // Ignore this request if the document is not in a live frame.
147 if (!document()->isActive())
150 // The Mozilla Full Screen API <https://wiki.mozilla.org/Gecko:FullScreenAPI> has different requirements
151 // for full screen mode, and do not have the concept of a full screen element stack.
152 bool inLegacyMozillaMode = (flags & Element::LEGACY_MOZILLA_REQUEST);
156 element = document()->documentElement();
158 // 1. If any of the following conditions are true, terminate these steps and queue a task to fire
159 // an event named fullscreenerror with its bubbles attribute set to true on the context object's
162 // The context object is not in a document.
163 if (!element->inDocument())
166 // The context object's node document, or an ancestor browsing context's document does not have
167 // the fullscreen enabled flag set.
168 if (checkType == EnforceIFrameAllowFullScreenRequirement && !fullScreenIsAllowedForElement(element))
171 // The context object's node document fullscreen element stack is not empty and its top element
172 // is not an ancestor of the context object. (NOTE: Ignore this requirement if the request was
173 // made via the legacy Mozilla-style API.)
174 if (!m_fullScreenElementStack.isEmpty() && !inLegacyMozillaMode) {
175 Element* lastElementOnStack = m_fullScreenElementStack.last().get();
176 if (lastElementOnStack == element || !lastElementOnStack->contains(element))
180 // A descendant browsing context's document has a non-empty fullscreen element stack.
181 bool descendentHasNonEmptyStack = false;
182 for (LocalFrame* descendant = document()->frame() ? document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext()) {
183 ASSERT(descendant->document());
184 if (fullscreenElementFrom(*descendant->document())) {
185 descendentHasNonEmptyStack = true;
189 if (descendentHasNonEmptyStack && !inLegacyMozillaMode)
192 // This algorithm is not allowed to show a pop-up:
193 // An algorithm is allowed to show a pop-up if, in the task in which the algorithm is running, either:
194 // - an activation behavior is currently being processed whose click event was trusted, or
195 // - the event listener for a trusted click event is being handled.
196 if (!UserGestureIndicator::processingUserGesture())
199 // There is a previously-established user preference, security risk, or platform limitation.
201 // 2. Let doc be element's node document. (i.e. "this")
202 Document* currentDoc = document();
204 // 3. Let docs be all doc's ancestor browsing context's documents (if any) and doc.
205 Deque<Document*> docs;
208 docs.prepend(currentDoc);
209 currentDoc = currentDoc->ownerElement() ? ¤tDoc->ownerElement()->document() : 0;
210 } while (currentDoc);
212 // 4. For each document in docs, run these substeps:
213 Deque<Document*>::iterator current = docs.begin(), following = docs.begin();
218 // 1. Let following document be the document after document in docs, or null if there is no
220 Document* currentDoc = *current;
221 Document* followingDoc = following != docs.end() ? *following : 0;
223 // 2. If following document is null, push context object on document's fullscreen element
224 // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
225 // set to true on the document.
227 from(*currentDoc).pushFullscreenElementStack(element);
228 addDocumentToFullScreenChangeEventQueue(currentDoc);
232 // 3. Otherwise, if document's fullscreen element stack is either empty or its top element
233 // is not following document's browsing context container,
234 Element* topElement = fullscreenElementFrom(*currentDoc);
235 if (!topElement || topElement != followingDoc->ownerElement()) {
236 // ...push following document's browsing context container on document's fullscreen element
237 // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
238 // set to true on document.
239 from(*currentDoc).pushFullscreenElementStack(followingDoc->ownerElement());
240 addDocumentToFullScreenChangeEventQueue(currentDoc);
244 // 4. Otherwise, do nothing for this document. It stays the same.
245 } while (++current != docs.end());
247 // 5. Return, and run the remaining steps asynchronously.
248 // 6. Optionally, perform some animation.
249 m_areKeysEnabledInFullScreen = flags & Element::ALLOW_KEYBOARD_INPUT;
250 document()->frameHost()->chrome().client().enterFullScreenForElement(element);
252 // 7. Optionally, display a message indicating how the user can exit displaying the context object fullscreen.
256 m_fullScreenErrorEventTargetQueue.append(element ? element : document()->documentElement());
257 m_fullScreenChangeDelayTimer.startOneShot(0, FROM_HERE);
260 void FullscreenElementStack::webkitCancelFullScreen()
262 // The Mozilla "cancelFullScreen()" API behaves like the W3C "fully exit fullscreen" behavior, which
264 // "To fully exit fullscreen act as if the exitFullscreen() method was invoked on the top-level browsing
265 // context's document and subsequently empty that document's fullscreen element stack."
266 if (!fullscreenElementFrom(document()->topDocument()))
269 // To achieve that aim, remove all the elements from the top document's stack except for the first before
270 // calling webkitExitFullscreen():
271 WillBeHeapVector<RefPtrWillBeMember<Element> > replacementFullscreenElementStack;
272 replacementFullscreenElementStack.append(fullscreenElementFrom(document()->topDocument()));
273 FullscreenElementStack& topFullscreenElementStack = from(document()->topDocument());
274 topFullscreenElementStack.m_fullScreenElementStack.swap(replacementFullscreenElementStack);
275 topFullscreenElementStack.webkitExitFullscreen();
278 void FullscreenElementStack::webkitExitFullscreen()
280 // The exitFullscreen() method must run these steps:
282 // 1. Let doc be the context object. (i.e. "this")
283 Document* currentDoc = document();
284 if (!currentDoc->isActive())
287 // 2. If doc's fullscreen element stack is empty, terminate these steps.
288 if (m_fullScreenElementStack.isEmpty())
291 // 3. Let descendants be all the doc's descendant browsing context's documents with a non-empty fullscreen
292 // element stack (if any), ordered so that the child of the doc is last and the document furthest
293 // away from the doc is first.
294 Deque<RefPtr<Document> > descendants;
295 for (LocalFrame* descendant = document()->frame() ? document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext()) {
296 ASSERT(descendant->document());
297 if (fullscreenElementFrom(*descendant->document()))
298 descendants.prepend(descendant->document());
301 // 4. For each descendant in descendants, empty descendant's fullscreen element stack, and queue a
302 // task to fire an event named fullscreenchange with its bubbles attribute set to true on descendant.
303 for (Deque<RefPtr<Document> >::iterator i = descendants.begin(); i != descendants.end(); ++i) {
305 from(**i).clearFullscreenElementStack();
306 addDocumentToFullScreenChangeEventQueue(i->get());
309 // 5. While doc is not null, run these substeps:
312 // 1. Pop the top element of doc's fullscreen element stack.
313 from(*currentDoc).popFullscreenElementStack();
315 // If doc's fullscreen element stack is non-empty and the element now at the top is either
316 // not in a document or its node document is not doc, repeat this substep.
317 newTop = fullscreenElementFrom(*currentDoc);
318 if (newTop && (!newTop->inDocument() || newTop->document() != currentDoc))
321 // 2. Queue a task to fire an event named fullscreenchange with its bubbles attribute set to true
323 addDocumentToFullScreenChangeEventQueue(currentDoc);
325 // 3. If doc's fullscreen element stack is empty and doc's browsing context has a browsing context
326 // container, set doc to that browsing context container's node document.
327 if (!newTop && currentDoc->ownerElement()) {
328 currentDoc = ¤tDoc->ownerElement()->document();
332 // 4. Otherwise, set doc to null.
336 // 6. Return, and run the remaining steps asynchronously.
337 // 7. Optionally, perform some animation.
339 FrameHost* host = document()->frameHost();
341 // Speculative fix for engaget.com/videos per crbug.com/336239.
342 // FIXME: This check is wrong. We ASSERT(document->isActive()) above
343 // so this should be redundant and should be removed!
347 // Only exit out of full screen window mode if there are no remaining elements in the
348 // full screen stack.
350 host->chrome().client().exitFullScreenForElement(m_fullScreenElement.get());
354 // Otherwise, notify the chrome of the new full screen element.
355 host->chrome().client().enterFullScreenForElement(newTop);
358 bool FullscreenElementStack::webkitFullscreenEnabled(Document& document)
360 // 4. The fullscreenEnabled attribute must return true if the context object and all ancestor
361 // browsing context's documents have their fullscreen enabled flag set, or false otherwise.
363 // Top-level browsing contexts are implied to have their allowFullScreen attribute set.
364 return fullscreenIsAllowedForAllOwners(document);
367 void FullscreenElementStack::webkitWillEnterFullScreenForElement(Element* element)
370 if (!document()->isActive())
373 if (m_fullScreenRenderer)
374 m_fullScreenRenderer->unwrapRenderer();
376 m_fullScreenElement = element;
378 // Create a placeholder block for a the full-screen element, to keep the page from reflowing
379 // when the element is removed from the normal flow. Only do this for a RenderBox, as only
380 // a box will have a frameRect. The placeholder will be created in setFullScreenRenderer()
382 RenderObject* renderer = m_fullScreenElement->renderer();
383 bool shouldCreatePlaceholder = renderer && renderer->isBox();
384 if (shouldCreatePlaceholder) {
385 m_savedPlaceholderFrameRect = toRenderBox(renderer)->frameRect();
386 m_savedPlaceholderRenderStyle = RenderStyle::clone(renderer->style());
389 if (m_fullScreenElement != document()->documentElement())
390 RenderFullScreen::wrapRenderer(renderer, renderer ? renderer->parent() : 0, document());
392 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
394 // FIXME: This should not call updateStyleIfNeeded.
395 document()->setNeedsStyleRecalc(SubtreeStyleChange);
396 document()->updateRenderTreeIfNeeded();
399 void FullscreenElementStack::webkitDidEnterFullScreenForElement(Element*)
401 if (!m_fullScreenElement)
404 if (!document()->isActive())
407 m_fullScreenElement->didBecomeFullscreenElement();
409 m_fullScreenChangeDelayTimer.startOneShot(0, FROM_HERE);
412 void FullscreenElementStack::webkitWillExitFullScreenForElement(Element*)
414 if (!m_fullScreenElement)
417 if (!document()->isActive())
420 m_fullScreenElement->willStopBeingFullscreenElement();
423 void FullscreenElementStack::webkitDidExitFullScreenForElement(Element*)
425 if (!m_fullScreenElement)
428 if (!document()->isActive())
431 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
433 m_areKeysEnabledInFullScreen = false;
435 if (m_fullScreenRenderer)
436 m_fullScreenRenderer->unwrapRenderer();
438 m_fullScreenElement = nullptr;
439 document()->setNeedsStyleRecalc(SubtreeStyleChange);
441 // When webkitCancelFullScreen is called, we call webkitExitFullScreen on the topDocument(). That
442 // means that the events will be queued there. So if we have no events here, start the timer on
443 // the exiting document.
444 Document* exitingDocument = document();
445 if (m_fullScreenChangeEventTargetQueue.isEmpty() && m_fullScreenErrorEventTargetQueue.isEmpty())
446 exitingDocument = &document()->topDocument();
447 ASSERT(exitingDocument);
448 from(*exitingDocument).m_fullScreenChangeDelayTimer.startOneShot(0, FROM_HERE);
451 void FullscreenElementStack::setFullScreenRenderer(RenderFullScreen* renderer)
453 if (renderer == m_fullScreenRenderer)
456 if (renderer && m_savedPlaceholderRenderStyle) {
457 renderer->createPlaceholder(m_savedPlaceholderRenderStyle.release(), m_savedPlaceholderFrameRect);
458 } else if (renderer && m_fullScreenRenderer && m_fullScreenRenderer->placeholder()) {
459 RenderBlock* placeholder = m_fullScreenRenderer->placeholder();
460 renderer->createPlaceholder(RenderStyle::clone(placeholder->style()), placeholder->frameRect());
463 if (m_fullScreenRenderer)
464 m_fullScreenRenderer->destroy();
465 ASSERT(!m_fullScreenRenderer);
467 m_fullScreenRenderer = renderer;
470 void FullscreenElementStack::fullScreenRendererDestroyed()
472 m_fullScreenRenderer = 0;
475 void FullscreenElementStack::fullScreenChangeDelayTimerFired(Timer<FullscreenElementStack>*)
477 // Since we dispatch events in this function, it's possible that the
478 // document will be detached and GC'd. We protect it here to make sure we
479 // can finish the function successfully.
480 RefPtrWillBeRawPtr<Document> protectDocument(document());
481 WillBeHeapDeque<RefPtrWillBeMember<Node> > changeQueue;
482 m_fullScreenChangeEventTargetQueue.swap(changeQueue);
483 WillBeHeapDeque<RefPtrWillBeMember<Node> > errorQueue;
484 m_fullScreenErrorEventTargetQueue.swap(errorQueue);
486 while (!changeQueue.isEmpty()) {
487 RefPtrWillBeRawPtr<Node> node = changeQueue.takeFirst();
489 node = document()->documentElement();
490 // The dispatchEvent below may have blown away our documentElement.
494 // If the element was removed from our tree, also message the documentElement. Since we may
495 // have a document hierarchy, check that node isn't in another document.
496 if (!document()->contains(node.get()) && !node->inDocument())
497 changeQueue.append(document()->documentElement());
499 node->dispatchEvent(Event::createBubble(EventTypeNames::webkitfullscreenchange));
502 while (!errorQueue.isEmpty()) {
503 RefPtrWillBeRawPtr<Node> node = errorQueue.takeFirst();
505 node = document()->documentElement();
506 // The dispatchEvent below may have blown away our documentElement.
510 // If the element was removed from our tree, also message the documentElement. Since we may
511 // have a document hierarchy, check that node isn't in another document.
512 if (!document()->contains(node.get()) && !node->inDocument())
513 errorQueue.append(document()->documentElement());
515 node->dispatchEvent(Event::createBubble(EventTypeNames::webkitfullscreenerror));
519 void FullscreenElementStack::fullScreenElementRemoved()
521 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
522 webkitCancelFullScreen();
525 void FullscreenElementStack::removeFullScreenElementOfSubtree(Node* node, bool amongChildrenOnly)
527 if (!m_fullScreenElement)
530 // If the node isn't in a document it can't have a fullscreen'd child.
531 if (!node->inDocument())
534 bool elementInSubtree = false;
535 if (amongChildrenOnly)
536 elementInSubtree = m_fullScreenElement->isDescendantOf(node);
538 elementInSubtree = (m_fullScreenElement == node) || m_fullScreenElement->isDescendantOf(node);
540 if (elementInSubtree)
541 fullScreenElementRemoved();
544 void FullscreenElementStack::clearFullscreenElementStack()
546 m_fullScreenElementStack.clear();
549 void FullscreenElementStack::popFullscreenElementStack()
551 if (m_fullScreenElementStack.isEmpty())
554 m_fullScreenElementStack.removeLast();
557 void FullscreenElementStack::pushFullscreenElementStack(Element* element)
559 m_fullScreenElementStack.append(element);
562 void FullscreenElementStack::addDocumentToFullScreenChangeEventQueue(Document* doc)
567 if (FullscreenElementStack* fullscreen = fromIfExists(*doc)) {
568 target = fullscreen->webkitFullscreenElement();
570 target = fullscreen->webkitCurrentFullScreenElement();
575 m_fullScreenChangeEventTargetQueue.append(target);
578 void FullscreenElementStack::trace(Visitor* visitor)
580 visitor->trace(m_fullScreenElement);
581 visitor->trace(m_fullScreenElementStack);
582 visitor->trace(m_fullScreenChangeEventTargetQueue);
583 visitor->trace(m_fullScreenErrorEventTargetQueue);
584 DocumentSupplement::trace(visitor);
587 } // namespace WebCore