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