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