• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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.
10  *
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.
15  *
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.
20  *
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.
25  *
26  */
27 
28 #include "config.h"
29 #include "core/dom/FullscreenElementStack.h"
30 
31 #include "HTMLNames.h"
32 #include "core/dom/Document.h"
33 #include "core/events/Event.h"
34 #include "core/html/HTMLFrameOwnerElement.h"
35 #include "core/page/Chrome.h"
36 #include "core/page/ChromeClient.h"
37 #include "core/frame/Frame.h"
38 #include "core/page/Page.h"
39 #include "core/frame/Settings.h"
40 #include "core/rendering/RenderFullScreen.h"
41 #include "platform/UserGestureIndicator.h"
42 
43 namespace WebCore {
44 
45 using namespace HTMLNames;
46 
isAttributeOnAllOwners(const WebCore::QualifiedName & attribute,const WebCore::QualifiedName & prefixedAttribute,const HTMLFrameOwnerElement * owner)47 static bool isAttributeOnAllOwners(const WebCore::QualifiedName& attribute, const WebCore::QualifiedName& prefixedAttribute, const HTMLFrameOwnerElement* owner)
48 {
49     if (!owner)
50         return true;
51     do {
52         if (!(owner->hasAttribute(attribute) || owner->hasAttribute(prefixedAttribute)))
53             return false;
54     } while ((owner = owner->document().ownerElement()));
55     return true;
56 }
57 
supplementName()58 const char* FullscreenElementStack::supplementName()
59 {
60     return "FullscreenElementStack";
61 }
62 
from(Document * document)63 FullscreenElementStack* FullscreenElementStack::from(Document* document)
64 {
65     FullscreenElementStack* fullscreen = fromIfExists(document);
66     if (!fullscreen) {
67         fullscreen = new FullscreenElementStack(document);
68         DocumentSupplement::provideTo(document, supplementName(), adoptPtr(fullscreen));
69     }
70 
71     return fullscreen;
72 }
73 
fromIfExistsSlow(Document * document)74 FullscreenElementStack* FullscreenElementStack::fromIfExistsSlow(Document* document)
75 {
76     return static_cast<FullscreenElementStack*>(DocumentSupplement::from(document, supplementName()));
77 }
78 
fullscreenElementFrom(Document * document)79 Element* FullscreenElementStack::fullscreenElementFrom(Document* document)
80 {
81     if (FullscreenElementStack* found = fromIfExists(document))
82         return found->webkitFullscreenElement();
83     return 0;
84 }
85 
currentFullScreenElementFrom(Document * document)86 Element* FullscreenElementStack::currentFullScreenElementFrom(Document* document)
87 {
88     if (FullscreenElementStack* found = fromIfExists(document))
89         return found->webkitCurrentFullScreenElement();
90     return 0;
91 }
92 
isFullScreen(Document * document)93 bool FullscreenElementStack::isFullScreen(Document* document)
94 {
95     if (FullscreenElementStack* found = fromIfExists(document))
96         return found->webkitIsFullScreen();
97     return false;
98 }
99 
FullscreenElementStack(Document * document)100 FullscreenElementStack::FullscreenElementStack(Document* document)
101     : DocumentLifecycleObserver(document)
102     , m_areKeysEnabledInFullScreen(false)
103     , m_fullScreenRenderer(0)
104     , m_fullScreenChangeDelayTimer(this, &FullscreenElementStack::fullScreenChangeDelayTimerFired)
105 {
106     document->setHasFullscreenElementStack();
107 }
108 
~FullscreenElementStack()109 FullscreenElementStack::~FullscreenElementStack()
110 {
111 }
112 
document()113 inline Document* FullscreenElementStack::document()
114 {
115     return lifecycleContext();
116 }
117 
documentWasDetached()118 void FullscreenElementStack::documentWasDetached()
119 {
120     m_fullScreenChangeEventTargetQueue.clear();
121     m_fullScreenErrorEventTargetQueue.clear();
122 
123     if (m_fullScreenRenderer)
124         setFullScreenRenderer(0);
125 }
126 
documentWasDisposed()127 void FullscreenElementStack::documentWasDisposed()
128 {
129     m_fullScreenElement = 0;
130     m_fullScreenElementStack.clear();
131 }
132 
fullScreenIsAllowedForElement(Element * element) const133 bool FullscreenElementStack::fullScreenIsAllowedForElement(Element* element) const
134 {
135     ASSERT(element);
136     return isAttributeOnAllOwners(allowfullscreenAttr, webkitallowfullscreenAttr, element->document().ownerElement());
137 }
138 
requestFullScreenForElement(Element * element,unsigned short flags,FullScreenCheckType checkType)139 void FullscreenElementStack::requestFullScreenForElement(Element* element, unsigned short flags, FullScreenCheckType checkType)
140 {
141     // The Mozilla Full Screen API <https://wiki.mozilla.org/Gecko:FullScreenAPI> has different requirements
142     // for full screen mode, and do not have the concept of a full screen element stack.
143     bool inLegacyMozillaMode = (flags & Element::LEGACY_MOZILLA_REQUEST);
144 
145     do {
146         if (!element)
147             element = document()->documentElement();
148 
149         // 1. If any of the following conditions are true, terminate these steps and queue a task to fire
150         // an event named fullscreenerror with its bubbles attribute set to true on the context object's
151         // node document:
152 
153         // The context object is not in a document.
154         if (!element->inDocument())
155             break;
156 
157         // The context object's node document, or an ancestor browsing context's document does not have
158         // the fullscreen enabled flag set.
159         if (checkType == EnforceIFrameAllowFullScreenRequirement && !fullScreenIsAllowedForElement(element))
160             break;
161 
162         // The context object's node document fullscreen element stack is not empty and its top element
163         // is not an ancestor of the context object. (NOTE: Ignore this requirement if the request was
164         // made via the legacy Mozilla-style API.)
165         if (!m_fullScreenElementStack.isEmpty() && !inLegacyMozillaMode) {
166             Element* lastElementOnStack = m_fullScreenElementStack.last().get();
167             if (lastElementOnStack == element || !lastElementOnStack->contains(element))
168                 break;
169         }
170 
171         // A descendant browsing context's document has a non-empty fullscreen element stack.
172         bool descendentHasNonEmptyStack = false;
173         for (Frame* descendant = document()->frame() ? document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext()) {
174             if (fullscreenElementFrom(descendant->document())) {
175                 descendentHasNonEmptyStack = true;
176                 break;
177             }
178         }
179         if (descendentHasNonEmptyStack && !inLegacyMozillaMode)
180             break;
181 
182         // This algorithm is not allowed to show a pop-up:
183         //   An algorithm is allowed to show a pop-up if, in the task in which the algorithm is running, either:
184         //   - an activation behavior is currently being processed whose click event was trusted, or
185         //   - the event listener for a trusted click event is being handled.
186         // FIXME: Does this need to null-check settings()?
187         if (!UserGestureIndicator::processingUserGesture() && (!element->isMediaElement() || document()->settings()->mediaFullscreenRequiresUserGesture()))
188             break;
189 
190         // There is a previously-established user preference, security risk, or platform limitation.
191         if (!document()->settings() || !document()->settings()->fullScreenEnabled())
192             break;
193 
194         // 2. Let doc be element's node document. (i.e. "this")
195         Document* currentDoc = document();
196 
197         // 3. Let docs be all doc's ancestor browsing context's documents (if any) and doc.
198         Deque<Document*> docs;
199 
200         do {
201             docs.prepend(currentDoc);
202             currentDoc = currentDoc->ownerElement() ? &currentDoc->ownerElement()->document() : 0;
203         } while (currentDoc);
204 
205         // 4. For each document in docs, run these substeps:
206         Deque<Document*>::iterator current = docs.begin(), following = docs.begin();
207 
208         do {
209             ++following;
210 
211             // 1. Let following document be the document after document in docs, or null if there is no
212             // such document.
213             Document* currentDoc = *current;
214             Document* followingDoc = following != docs.end() ? *following : 0;
215 
216             // 2. If following document is null, push context object on document's fullscreen element
217             // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
218             // set to true on the document.
219             if (!followingDoc) {
220                 from(currentDoc)->pushFullscreenElementStack(element);
221                 addDocumentToFullScreenChangeEventQueue(currentDoc);
222                 continue;
223             }
224 
225             // 3. Otherwise, if document's fullscreen element stack is either empty or its top element
226             // is not following document's browsing context container,
227             Element* topElement = fullscreenElementFrom(currentDoc);
228             if (!topElement || topElement != followingDoc->ownerElement()) {
229                 // ...push following document's browsing context container on document's fullscreen element
230                 // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
231                 // set to true on document.
232                 from(currentDoc)->pushFullscreenElementStack(followingDoc->ownerElement());
233                 addDocumentToFullScreenChangeEventQueue(currentDoc);
234                 continue;
235             }
236 
237             // 4. Otherwise, do nothing for this document. It stays the same.
238         } while (++current != docs.end());
239 
240         // 5. Return, and run the remaining steps asynchronously.
241         // 6. Optionally, perform some animation.
242         m_areKeysEnabledInFullScreen = flags & Element::ALLOW_KEYBOARD_INPUT;
243         document()->page()->chrome().client().enterFullScreenForElement(element);
244 
245         // 7. Optionally, display a message indicating how the user can exit displaying the context object fullscreen.
246         return;
247     } while (0);
248 
249     m_fullScreenErrorEventTargetQueue.append(element ? element : document()->documentElement());
250     m_fullScreenChangeDelayTimer.startOneShot(0);
251 }
252 
webkitCancelFullScreen()253 void FullscreenElementStack::webkitCancelFullScreen()
254 {
255     // The Mozilla "cancelFullScreen()" API behaves like the W3C "fully exit fullscreen" behavior, which
256     // is defined as:
257     // "To fully exit fullscreen act as if the exitFullscreen() method was invoked on the top-level browsing
258     // context's document and subsequently empty that document's fullscreen element stack."
259     if (!fullscreenElementFrom(document()->topDocument()))
260         return;
261 
262     // To achieve that aim, remove all the elements from the top document's stack except for the first before
263     // calling webkitExitFullscreen():
264     Vector<RefPtr<Element> > replacementFullscreenElementStack;
265     replacementFullscreenElementStack.append(fullscreenElementFrom(document()->topDocument()));
266     FullscreenElementStack* topFullscreenElementStack = from(document()->topDocument());
267     topFullscreenElementStack->m_fullScreenElementStack.swap(replacementFullscreenElementStack);
268     topFullscreenElementStack->webkitExitFullscreen();
269 }
270 
webkitExitFullscreen()271 void FullscreenElementStack::webkitExitFullscreen()
272 {
273     // The exitFullscreen() method must run these steps:
274 
275     // 1. Let doc be the context object. (i.e. "this")
276     Document* currentDoc = document();
277 
278     // 2. If doc's fullscreen element stack is empty, terminate these steps.
279     if (m_fullScreenElementStack.isEmpty())
280         return;
281 
282     // 3. Let descendants be all the doc's descendant browsing context's documents with a non-empty fullscreen
283     // element stack (if any), ordered so that the child of the doc is last and the document furthest
284     // away from the doc is first.
285     Deque<RefPtr<Document> > descendants;
286     for (Frame* descendant = document()->frame() ?  document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext()) {
287         if (fullscreenElementFrom(descendant->document()))
288             descendants.prepend(descendant->document());
289     }
290 
291     // 4. For each descendant in descendants, empty descendant's fullscreen element stack, and queue a
292     // task to fire an event named fullscreenchange with its bubbles attribute set to true on descendant.
293     for (Deque<RefPtr<Document> >::iterator i = descendants.begin(); i != descendants.end(); ++i) {
294         from(i->get())->clearFullscreenElementStack();
295         addDocumentToFullScreenChangeEventQueue(i->get());
296     }
297 
298     // 5. While doc is not null, run these substeps:
299     Element* newTop = 0;
300     while (currentDoc) {
301         // 1. Pop the top element of doc's fullscreen element stack.
302         from(currentDoc)->popFullscreenElementStack();
303 
304         //    If doc's fullscreen element stack is non-empty and the element now at the top is either
305         //    not in a document or its node document is not doc, repeat this substep.
306         newTop = fullscreenElementFrom(currentDoc);
307         if (newTop && (!newTop->inDocument() || newTop->document() != currentDoc))
308             continue;
309 
310         // 2. Queue a task to fire an event named fullscreenchange with its bubbles attribute set to true
311         // on doc.
312         addDocumentToFullScreenChangeEventQueue(currentDoc);
313 
314         // 3. If doc's fullscreen element stack is empty and doc's browsing context has a browsing context
315         // container, set doc to that browsing context container's node document.
316         if (!newTop && currentDoc->ownerElement()) {
317             currentDoc = &currentDoc->ownerElement()->document();
318             continue;
319         }
320 
321         // 4. Otherwise, set doc to null.
322         currentDoc = 0;
323     }
324 
325     // 6. Return, and run the remaining steps asynchronously.
326     // 7. Optionally, perform some animation.
327 
328     if (!document()->page())
329         return;
330 
331     // Only exit out of full screen window mode if there are no remaining elements in the
332     // full screen stack.
333     if (!newTop) {
334         document()->page()->chrome().client().exitFullScreenForElement(m_fullScreenElement.get());
335         return;
336     }
337 
338     // Otherwise, notify the chrome of the new full screen element.
339     document()->page()->chrome().client().enterFullScreenForElement(newTop);
340 }
341 
webkitFullscreenEnabled(Document * document)342 bool FullscreenElementStack::webkitFullscreenEnabled(Document* document)
343 {
344     // 4. The fullscreenEnabled attribute must return true if the context object and all ancestor
345     // browsing context's documents have their fullscreen enabled flag set, or false otherwise.
346 
347     // Top-level browsing contexts are implied to have their allowFullScreen attribute set.
348     return isAttributeOnAllOwners(allowfullscreenAttr, webkitallowfullscreenAttr, document->ownerElement());
349 
350 }
351 
webkitWillEnterFullScreenForElement(Element * element)352 void FullscreenElementStack::webkitWillEnterFullScreenForElement(Element* element)
353 {
354     if (!document()->isActive())
355         return;
356 
357     ASSERT(element);
358 
359     // Protect against being called after the document has been removed from the page.
360     if (!document()->settings())
361         return;
362 
363     ASSERT(document()->settings()->fullScreenEnabled());
364 
365     if (m_fullScreenRenderer)
366         m_fullScreenRenderer->unwrapRenderer();
367 
368     m_fullScreenElement = element;
369 
370     // Create a placeholder block for a the full-screen element, to keep the page from reflowing
371     // when the element is removed from the normal flow. Only do this for a RenderBox, as only
372     // a box will have a frameRect. The placeholder will be created in setFullScreenRenderer()
373     // during layout.
374     RenderObject* renderer = m_fullScreenElement->renderer();
375     bool shouldCreatePlaceholder = renderer && renderer->isBox();
376     if (shouldCreatePlaceholder) {
377         m_savedPlaceholderFrameRect = toRenderBox(renderer)->frameRect();
378         m_savedPlaceholderRenderStyle = RenderStyle::clone(renderer->style());
379     }
380 
381     if (m_fullScreenElement != document()->documentElement())
382         RenderFullScreen::wrapRenderer(renderer, renderer ? renderer->parent() : 0, document());
383 
384     m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
385 
386     document()->recalcStyle(Force);
387 }
388 
webkitDidEnterFullScreenForElement(Element *)389 void FullscreenElementStack::webkitDidEnterFullScreenForElement(Element*)
390 {
391     if (!m_fullScreenElement)
392         return;
393 
394     if (!document()->isActive())
395         return;
396 
397     m_fullScreenElement->didBecomeFullscreenElement();
398 
399     m_fullScreenChangeDelayTimer.startOneShot(0);
400 }
401 
webkitWillExitFullScreenForElement(Element *)402 void FullscreenElementStack::webkitWillExitFullScreenForElement(Element*)
403 {
404     if (!m_fullScreenElement)
405         return;
406 
407     if (!document()->isActive())
408         return;
409 
410     m_fullScreenElement->willStopBeingFullscreenElement();
411 }
412 
webkitDidExitFullScreenForElement(Element *)413 void FullscreenElementStack::webkitDidExitFullScreenForElement(Element*)
414 {
415     if (!m_fullScreenElement)
416         return;
417 
418     if (!document()->isActive())
419         return;
420 
421     m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
422 
423     m_areKeysEnabledInFullScreen = false;
424 
425     if (m_fullScreenRenderer)
426         m_fullScreenRenderer->unwrapRenderer();
427 
428     m_fullScreenElement = 0;
429     document()->setNeedsStyleRecalc();
430 
431     // When webkitCancelFullScreen is called, we call webkitExitFullScreen on the topDocument(). That
432     // means that the events will be queued there. So if we have no events here, start the timer on
433     // the exiting document.
434     Document* exitingDocument = document();
435     if (m_fullScreenChangeEventTargetQueue.isEmpty() && m_fullScreenErrorEventTargetQueue.isEmpty())
436         exitingDocument = document()->topDocument();
437     from(exitingDocument)->m_fullScreenChangeDelayTimer.startOneShot(0);
438 }
439 
setFullScreenRenderer(RenderFullScreen * renderer)440 void FullscreenElementStack::setFullScreenRenderer(RenderFullScreen* renderer)
441 {
442     if (renderer == m_fullScreenRenderer)
443         return;
444 
445     if (renderer && m_savedPlaceholderRenderStyle) {
446         renderer->createPlaceholder(m_savedPlaceholderRenderStyle.release(), m_savedPlaceholderFrameRect);
447     } else if (renderer && m_fullScreenRenderer && m_fullScreenRenderer->placeholder()) {
448         RenderBlock* placeholder = m_fullScreenRenderer->placeholder();
449         renderer->createPlaceholder(RenderStyle::clone(placeholder->style()), placeholder->frameRect());
450     }
451 
452     if (m_fullScreenRenderer)
453         m_fullScreenRenderer->destroy();
454     ASSERT(!m_fullScreenRenderer);
455 
456     m_fullScreenRenderer = renderer;
457 }
458 
fullScreenRendererDestroyed()459 void FullscreenElementStack::fullScreenRendererDestroyed()
460 {
461     m_fullScreenRenderer = 0;
462 }
463 
fullScreenChangeDelayTimerFired(Timer<FullscreenElementStack> *)464 void FullscreenElementStack::fullScreenChangeDelayTimerFired(Timer<FullscreenElementStack>*)
465 {
466     // Since we dispatch events in this function, it's possible that the
467     // document will be detached and GC'd. We protect it here to make sure we
468     // can finish the function successfully.
469     RefPtr<Document> protectDocument(document());
470     Deque<RefPtr<Node> > changeQueue;
471     m_fullScreenChangeEventTargetQueue.swap(changeQueue);
472     Deque<RefPtr<Node> > errorQueue;
473     m_fullScreenErrorEventTargetQueue.swap(errorQueue);
474 
475     while (!changeQueue.isEmpty()) {
476         RefPtr<Node> node = changeQueue.takeFirst();
477         if (!node)
478             node = document()->documentElement();
479         // The dispatchEvent below may have blown away our documentElement.
480         if (!node)
481             continue;
482 
483         // If the element was removed from our tree, also message the documentElement. Since we may
484         // have a document hierarchy, check that node isn't in another document.
485         if (!document()->contains(node.get()) && !node->inDocument())
486             changeQueue.append(document()->documentElement());
487 
488         node->dispatchEvent(Event::createBubble(EventTypeNames::webkitfullscreenchange));
489     }
490 
491     while (!errorQueue.isEmpty()) {
492         RefPtr<Node> node = errorQueue.takeFirst();
493         if (!node)
494             node = document()->documentElement();
495         // The dispatchEvent below may have blown away our documentElement.
496         if (!node)
497             continue;
498 
499         // If the element was removed from our tree, also message the documentElement. Since we may
500         // have a document hierarchy, check that node isn't in another document.
501         if (!document()->contains(node.get()) && !node->inDocument())
502             errorQueue.append(document()->documentElement());
503 
504         node->dispatchEvent(Event::createBubble(EventTypeNames::webkitfullscreenerror));
505     }
506 }
507 
fullScreenElementRemoved()508 void FullscreenElementStack::fullScreenElementRemoved()
509 {
510     m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
511     webkitCancelFullScreen();
512 }
513 
removeFullScreenElementOfSubtree(Node * node,bool amongChildrenOnly)514 void FullscreenElementStack::removeFullScreenElementOfSubtree(Node* node, bool amongChildrenOnly)
515 {
516     if (!m_fullScreenElement)
517         return;
518 
519     // If the node isn't in a document it can't have a fullscreen'd child.
520     if (!node->inDocument())
521         return;
522 
523     bool elementInSubtree = false;
524     if (amongChildrenOnly)
525         elementInSubtree = m_fullScreenElement->isDescendantOf(node);
526     else
527         elementInSubtree = (m_fullScreenElement == node) || m_fullScreenElement->isDescendantOf(node);
528 
529     if (elementInSubtree)
530         fullScreenElementRemoved();
531 }
532 
clearFullscreenElementStack()533 void FullscreenElementStack::clearFullscreenElementStack()
534 {
535     m_fullScreenElementStack.clear();
536 }
537 
popFullscreenElementStack()538 void FullscreenElementStack::popFullscreenElementStack()
539 {
540     if (m_fullScreenElementStack.isEmpty())
541         return;
542 
543     m_fullScreenElementStack.removeLast();
544 }
545 
pushFullscreenElementStack(Element * element)546 void FullscreenElementStack::pushFullscreenElementStack(Element* element)
547 {
548     m_fullScreenElementStack.append(element);
549 }
550 
addDocumentToFullScreenChangeEventQueue(Document * doc)551 void FullscreenElementStack::addDocumentToFullScreenChangeEventQueue(Document* doc)
552 {
553     ASSERT(doc);
554 
555     Node* target = 0;
556     if (FullscreenElementStack* fullscreen = fromIfExists(doc)) {
557         target = fullscreen->webkitFullscreenElement();
558         if (!target)
559             target = fullscreen->webkitCurrentFullScreenElement();
560     }
561 
562     if (!target)
563         target = doc;
564     m_fullScreenChangeEventTargetQueue.append(target);
565 }
566 
567 } // namespace WebCore
568