Home | History | Annotate | Download | only in dom
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  *           (C) 2001 Dirk Mueller (mueller (at) kde.org)
      5  *           (C) 2006 Alexey Proskuryakov (ap (at) 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 
     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 
     58 const char* FullscreenElementStack::supplementName()
     59 {
     60     return "FullscreenElementStack";
     61 }
     62 
     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 
     74 FullscreenElementStack* FullscreenElementStack::fromIfExistsSlow(Document* document)
     75 {
     76     return static_cast<FullscreenElementStack*>(DocumentSupplement::from(document, supplementName()));
     77 }
     78 
     79 Element* FullscreenElementStack::fullscreenElementFrom(Document* document)
     80 {
     81     if (FullscreenElementStack* found = fromIfExists(document))
     82         return found->webkitFullscreenElement();
     83     return 0;
     84 }
     85 
     86 Element* FullscreenElementStack::currentFullScreenElementFrom(Document* document)
     87 {
     88     if (FullscreenElementStack* found = fromIfExists(document))
     89         return found->webkitCurrentFullScreenElement();
     90     return 0;
     91 }
     92 
     93 bool FullscreenElementStack::isFullScreen(Document* document)
     94 {
     95     if (FullscreenElementStack* found = fromIfExists(document))
     96         return found->webkitIsFullScreen();
     97     return false;
     98 }
     99 
    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 
    109 FullscreenElementStack::~FullscreenElementStack()
    110 {
    111 }
    112 
    113 inline Document* FullscreenElementStack::document()
    114 {
    115     return lifecycleContext();
    116 }
    117 
    118 void FullscreenElementStack::documentWasDetached()
    119 {
    120     m_fullScreenChangeEventTargetQueue.clear();
    121     m_fullScreenErrorEventTargetQueue.clear();
    122 
    123     if (m_fullScreenRenderer)
    124         setFullScreenRenderer(0);
    125 }
    126 
    127 void FullscreenElementStack::documentWasDisposed()
    128 {
    129     m_fullScreenElement = 0;
    130     m_fullScreenElementStack.clear();
    131 }
    132 
    133 bool FullscreenElementStack::fullScreenIsAllowedForElement(Element* element) const
    134 {
    135     ASSERT(element);
    136     return isAttributeOnAllOwners(allowfullscreenAttr, webkitallowfullscreenAttr, element->document().ownerElement());
    137 }
    138 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    459 void FullscreenElementStack::fullScreenRendererDestroyed()
    460 {
    461     m_fullScreenRenderer = 0;
    462 }
    463 
    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 
    508 void FullscreenElementStack::fullScreenElementRemoved()
    509 {
    510     m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
    511     webkitCancelFullScreen();
    512 }
    513 
    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 
    533 void FullscreenElementStack::clearFullscreenElementStack()
    534 {
    535     m_fullScreenElementStack.clear();
    536 }
    537 
    538 void FullscreenElementStack::popFullscreenElementStack()
    539 {
    540     if (m_fullScreenElementStack.isEmpty())
    541         return;
    542 
    543     m_fullScreenElementStack.removeLast();
    544 }
    545 
    546 void FullscreenElementStack::pushFullscreenElementStack(Element* element)
    547 {
    548     m_fullScreenElementStack.append(element);
    549 }
    550 
    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