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