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