22875bfdc6440676f30dc17a5338acdcf3372529
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / dom / FullscreenElementStack.cpp
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/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"
42
43 namespace WebCore {
44
45 using namespace HTMLNames;
46
47 static bool fullscreenIsAllowedForAllOwners(const Document& document)
48 {
49     const HTMLFrameOwnerElement* owner = document.ownerElement();
50     if (!owner)
51         return true;
52     do {
53         if (!owner->hasAttribute(allowfullscreenAttr)) {
54             if (owner->hasAttribute(webkitallowfullscreenAttr))
55                 UseCounter::count(document, UseCounter::PrefixedAllowFullscreenAttribute);
56             else
57                 return false;
58         }
59     } while ((owner = owner->document().ownerElement()));
60     return true;
61 }
62
63 const char* FullscreenElementStack::supplementName()
64 {
65     return "FullscreenElementStack";
66 }
67
68 FullscreenElementStack& FullscreenElementStack::from(Document& document)
69 {
70     FullscreenElementStack* fullscreen = fromIfExists(document);
71     if (!fullscreen) {
72         fullscreen = new FullscreenElementStack(document);
73         DocumentSupplement::provideTo(document, supplementName(), adoptPtrWillBeNoop(fullscreen));
74     }
75
76     return *fullscreen;
77 }
78
79 FullscreenElementStack* FullscreenElementStack::fromIfExistsSlow(Document& document)
80 {
81     return static_cast<FullscreenElementStack*>(DocumentSupplement::from(document, supplementName()));
82 }
83
84 Element* FullscreenElementStack::fullscreenElementFrom(Document& document)
85 {
86     if (FullscreenElementStack* found = fromIfExists(document))
87         return found->webkitFullscreenElement();
88     return 0;
89 }
90
91 Element* FullscreenElementStack::currentFullScreenElementFrom(Document& document)
92 {
93     if (FullscreenElementStack* found = fromIfExists(document))
94         return found->webkitCurrentFullScreenElement();
95     return 0;
96 }
97
98 bool FullscreenElementStack::isFullScreen(Document& document)
99 {
100     if (FullscreenElementStack* found = fromIfExists(document))
101         return found->webkitIsFullScreen();
102     return false;
103 }
104
105 FullscreenElementStack::FullscreenElementStack(Document& document)
106     : DocumentLifecycleObserver(&document)
107     , m_areKeysEnabledInFullScreen(false)
108     , m_fullScreenRenderer(0)
109     , m_fullScreenChangeDelayTimer(this, &FullscreenElementStack::fullScreenChangeDelayTimerFired)
110 {
111     document.setHasFullscreenElementStack();
112 }
113
114 FullscreenElementStack::~FullscreenElementStack()
115 {
116 }
117
118 inline Document* FullscreenElementStack::document()
119 {
120     return lifecycleContext();
121 }
122
123 void FullscreenElementStack::documentWasDetached()
124 {
125     m_fullScreenChangeEventTargetQueue.clear();
126     m_fullScreenErrorEventTargetQueue.clear();
127
128     if (m_fullScreenRenderer)
129         setFullScreenRenderer(0);
130 }
131
132 void FullscreenElementStack::documentWasDisposed()
133 {
134     m_fullScreenElement = nullptr;
135     m_fullScreenElementStack.clear();
136 }
137
138 bool FullscreenElementStack::fullScreenIsAllowedForElement(Element* element) const
139 {
140     ASSERT(element);
141     return fullscreenIsAllowedForAllOwners(element->document());
142 }
143
144 void FullscreenElementStack::requestFullScreenForElement(Element* element, unsigned short flags, FullScreenCheckType checkType)
145 {
146     // Ignore this request if the document is not in a live frame.
147     if (!document()->isActive())
148         return;
149
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);
153
154     do {
155         if (!element)
156             element = document()->documentElement();
157
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
160         // node document:
161
162         // The context object is not in a document.
163         if (!element->inDocument())
164             break;
165
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))
169             break;
170
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))
177                 break;
178         }
179
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;
186                 break;
187             }
188         }
189         if (descendentHasNonEmptyStack && !inLegacyMozillaMode)
190             break;
191
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())
197             break;
198
199         // There is a previously-established user preference, security risk, or platform limitation.
200
201         // 2. Let doc be element's node document. (i.e. "this")
202         Document* currentDoc = document();
203
204         // 3. Let docs be all doc's ancestor browsing context's documents (if any) and doc.
205         Deque<Document*> docs;
206
207         do {
208             docs.prepend(currentDoc);
209             currentDoc = currentDoc->ownerElement() ? &currentDoc->ownerElement()->document() : 0;
210         } while (currentDoc);
211
212         // 4. For each document in docs, run these substeps:
213         Deque<Document*>::iterator current = docs.begin(), following = docs.begin();
214
215         do {
216             ++following;
217
218             // 1. Let following document be the document after document in docs, or null if there is no
219             // such document.
220             Document* currentDoc = *current;
221             Document* followingDoc = following != docs.end() ? *following : 0;
222
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.
226             if (!followingDoc) {
227                 from(*currentDoc).pushFullscreenElementStack(element);
228                 addDocumentToFullScreenChangeEventQueue(currentDoc);
229                 continue;
230             }
231
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);
241                 continue;
242             }
243
244             // 4. Otherwise, do nothing for this document. It stays the same.
245         } while (++current != docs.end());
246
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);
251
252         // 7. Optionally, display a message indicating how the user can exit displaying the context object fullscreen.
253         return;
254     } while (0);
255
256     m_fullScreenErrorEventTargetQueue.append(element ? element : document()->documentElement());
257     m_fullScreenChangeDelayTimer.startOneShot(0, FROM_HERE);
258 }
259
260 void FullscreenElementStack::webkitCancelFullScreen()
261 {
262     // The Mozilla "cancelFullScreen()" API behaves like the W3C "fully exit fullscreen" behavior, which
263     // is defined as:
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()))
267         return;
268
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();
276 }
277
278 void FullscreenElementStack::webkitExitFullscreen()
279 {
280     // The exitFullscreen() method must run these steps:
281
282     // 1. Let doc be the context object. (i.e. "this")
283     Document* currentDoc = document();
284     if (!currentDoc->isActive())
285         return;
286
287     // 2. If doc's fullscreen element stack is empty, terminate these steps.
288     if (m_fullScreenElementStack.isEmpty())
289         return;
290
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());
299     }
300
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) {
304         ASSERT(*i);
305         from(**i).clearFullscreenElementStack();
306         addDocumentToFullScreenChangeEventQueue(i->get());
307     }
308
309     // 5. While doc is not null, run these substeps:
310     Element* newTop = 0;
311     while (currentDoc) {
312         // 1. Pop the top element of doc's fullscreen element stack.
313         from(*currentDoc).popFullscreenElementStack();
314
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))
319             continue;
320
321         // 2. Queue a task to fire an event named fullscreenchange with its bubbles attribute set to true
322         // on doc.
323         addDocumentToFullScreenChangeEventQueue(currentDoc);
324
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 = &currentDoc->ownerElement()->document();
329             continue;
330         }
331
332         // 4. Otherwise, set doc to null.
333         currentDoc = 0;
334     }
335
336     // 6. Return, and run the remaining steps asynchronously.
337     // 7. Optionally, perform some animation.
338
339     FrameHost* host = document()->frameHost();
340
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!
344     if (!host)
345         return;
346
347     // Only exit out of full screen window mode if there are no remaining elements in the
348     // full screen stack.
349     if (!newTop) {
350         host->chrome().client().exitFullScreenForElement(m_fullScreenElement.get());
351         return;
352     }
353
354     // Otherwise, notify the chrome of the new full screen element.
355     host->chrome().client().enterFullScreenForElement(newTop);
356 }
357
358 bool FullscreenElementStack::webkitFullscreenEnabled(Document& document)
359 {
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.
362
363     // Top-level browsing contexts are implied to have their allowFullScreen attribute set.
364     return fullscreenIsAllowedForAllOwners(document);
365 }
366
367 void FullscreenElementStack::webkitWillEnterFullScreenForElement(Element* element)
368 {
369     ASSERT(element);
370     if (!document()->isActive())
371         return;
372
373     if (m_fullScreenRenderer)
374         m_fullScreenRenderer->unwrapRenderer();
375
376     m_fullScreenElement = element;
377
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()
381     // during layout.
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());
387     }
388
389     if (m_fullScreenElement != document()->documentElement())
390         RenderFullScreen::wrapRenderer(renderer, renderer ? renderer->parent() : 0, document());
391
392     m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
393
394     // FIXME: This should not call updateStyleIfNeeded.
395     document()->setNeedsStyleRecalc(SubtreeStyleChange);
396     document()->updateRenderTreeIfNeeded();
397 }
398
399 void FullscreenElementStack::webkitDidEnterFullScreenForElement(Element*)
400 {
401     if (!m_fullScreenElement)
402         return;
403
404     if (!document()->isActive())
405         return;
406
407     m_fullScreenElement->didBecomeFullscreenElement();
408
409     m_fullScreenChangeDelayTimer.startOneShot(0, FROM_HERE);
410 }
411
412 void FullscreenElementStack::webkitWillExitFullScreenForElement(Element*)
413 {
414     if (!m_fullScreenElement)
415         return;
416
417     if (!document()->isActive())
418         return;
419
420     m_fullScreenElement->willStopBeingFullscreenElement();
421 }
422
423 void FullscreenElementStack::webkitDidExitFullScreenForElement(Element*)
424 {
425     if (!m_fullScreenElement)
426         return;
427
428     if (!document()->isActive())
429         return;
430
431     m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
432
433     m_areKeysEnabledInFullScreen = false;
434
435     if (m_fullScreenRenderer)
436         m_fullScreenRenderer->unwrapRenderer();
437
438     m_fullScreenElement = nullptr;
439     document()->setNeedsStyleRecalc(SubtreeStyleChange);
440
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);
449 }
450
451 void FullscreenElementStack::setFullScreenRenderer(RenderFullScreen* renderer)
452 {
453     if (renderer == m_fullScreenRenderer)
454         return;
455
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());
461     }
462
463     if (m_fullScreenRenderer)
464         m_fullScreenRenderer->destroy();
465     ASSERT(!m_fullScreenRenderer);
466
467     m_fullScreenRenderer = renderer;
468 }
469
470 void FullscreenElementStack::fullScreenRendererDestroyed()
471 {
472     m_fullScreenRenderer = 0;
473 }
474
475 void FullscreenElementStack::fullScreenChangeDelayTimerFired(Timer<FullscreenElementStack>*)
476 {
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);
485
486     while (!changeQueue.isEmpty()) {
487         RefPtrWillBeRawPtr<Node> node = changeQueue.takeFirst();
488         if (!node)
489             node = document()->documentElement();
490         // The dispatchEvent below may have blown away our documentElement.
491         if (!node)
492             continue;
493
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());
498
499         node->dispatchEvent(Event::createBubble(EventTypeNames::webkitfullscreenchange));
500     }
501
502     while (!errorQueue.isEmpty()) {
503         RefPtrWillBeRawPtr<Node> node = errorQueue.takeFirst();
504         if (!node)
505             node = document()->documentElement();
506         // The dispatchEvent below may have blown away our documentElement.
507         if (!node)
508             continue;
509
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());
514
515         node->dispatchEvent(Event::createBubble(EventTypeNames::webkitfullscreenerror));
516     }
517 }
518
519 void FullscreenElementStack::fullScreenElementRemoved()
520 {
521     m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
522     webkitCancelFullScreen();
523 }
524
525 void FullscreenElementStack::removeFullScreenElementOfSubtree(Node* node, bool amongChildrenOnly)
526 {
527     if (!m_fullScreenElement)
528         return;
529
530     // If the node isn't in a document it can't have a fullscreen'd child.
531     if (!node->inDocument())
532         return;
533
534     bool elementInSubtree = false;
535     if (amongChildrenOnly)
536         elementInSubtree = m_fullScreenElement->isDescendantOf(node);
537     else
538         elementInSubtree = (m_fullScreenElement == node) || m_fullScreenElement->isDescendantOf(node);
539
540     if (elementInSubtree)
541         fullScreenElementRemoved();
542 }
543
544 void FullscreenElementStack::clearFullscreenElementStack()
545 {
546     m_fullScreenElementStack.clear();
547 }
548
549 void FullscreenElementStack::popFullscreenElementStack()
550 {
551     if (m_fullScreenElementStack.isEmpty())
552         return;
553
554     m_fullScreenElementStack.removeLast();
555 }
556
557 void FullscreenElementStack::pushFullscreenElementStack(Element* element)
558 {
559     m_fullScreenElementStack.append(element);
560 }
561
562 void FullscreenElementStack::addDocumentToFullScreenChangeEventQueue(Document* doc)
563 {
564     ASSERT(doc);
565
566     Node* target = 0;
567     if (FullscreenElementStack* fullscreen = fromIfExists(*doc)) {
568         target = fullscreen->webkitFullscreenElement();
569         if (!target)
570             target = fullscreen->webkitCurrentFullScreenElement();
571     }
572
573     if (!target)
574         target = doc;
575     m_fullScreenChangeEventTargetQueue.append(target);
576 }
577
578 void FullscreenElementStack::trace(Visitor* visitor)
579 {
580     visitor->trace(m_fullScreenElement);
581     visitor->trace(m_fullScreenElementStack);
582     visitor->trace(m_fullScreenChangeEventTargetQueue);
583     visitor->trace(m_fullScreenErrorEventTargetQueue);
584     DocumentSupplement::trace(visitor);
585 }
586
587 } // namespace WebCore