Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / core / dom / Fullscreen.cpp
blob110dec35632e5025f4c97611f0e4d89f4a900b58
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.
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.
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.
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.
28 #include "config.h"
29 #include "core/dom/Fullscreen.h"
31 #include "core/HTMLNames.h"
32 #include "core/dom/Document.h"
33 #include "core/dom/ElementTraversal.h"
34 #include "core/events/Event.h"
35 #include "core/frame/FrameHost.h"
36 #include "core/frame/LocalFrame.h"
37 #include "core/frame/OriginsUsingFeatures.h"
38 #include "core/frame/Settings.h"
39 #include "core/frame/UseCounter.h"
40 #include "core/html/HTMLIFrameElement.h"
41 #include "core/html/HTMLMediaElement.h"
42 #include "core/input/EventHandler.h"
43 #include "core/inspector/ConsoleMessage.h"
44 #include "core/layout/LayoutFullScreen.h"
45 #include "core/page/ChromeClient.h"
46 #include "platform/UserGestureIndicator.h"
48 namespace blink {
50 using namespace HTMLNames;
52 static bool fullscreenIsAllowedForAllOwners(const Document& document)
54 for (const Element* owner = document.ownerElement(); owner; owner = owner->document().ownerElement()) {
55 if (!isHTMLIFrameElement(owner))
56 return false;
57 if (!owner->hasAttribute(allowfullscreenAttr))
58 return false;
60 return true;
63 static bool fullscreenIsSupported(const Document& document)
65 // Fullscreen is supported if there is no previously-established user preference,
66 // security risk, or platform limitation.
67 return !document.settings() || document.settings()->fullscreenSupported();
70 static bool fullscreenElementReady(const Element& element, Fullscreen::RequestType requestType)
72 // A fullscreen element ready check for an element |element| returns true if all of the
73 // following are true, and false otherwise:
75 // |element| is in a document.
76 if (!element.inDocument())
77 return false;
79 // |element|'s node document's fullscreen enabled flag is set.
80 if (!fullscreenIsAllowedForAllOwners(element.document()))
81 return false;
83 // |element|'s node document's fullscreen element stack is either empty or its top element is an
84 // inclusive ancestor of |element|.
85 if (const Element* topElement = Fullscreen::fullscreenElementFrom(element.document())) {
86 if (!topElement->contains(&element))
87 return false;
90 // |element| has no ancestor element whose local name is iframe and namespace is the HTML
91 // namespace.
92 if (Traversal<HTMLIFrameElement>::firstAncestor(element))
93 return false;
95 // |element|'s node document's browsing context either has a browsing context container and the
96 // fullscreen element ready check returns true for |element|'s node document's browsing
97 // context's browsing context container, or it has no browsing context container.
98 if (const Element* owner = element.document().ownerElement()) {
99 if (!fullscreenElementReady(*owner, requestType))
100 return false;
103 return true;
106 static bool isPrefixed(const AtomicString& type)
108 return type == EventTypeNames::webkitfullscreenchange || type == EventTypeNames::webkitfullscreenerror;
111 static PassRefPtrWillBeRawPtr<Event> createEvent(const AtomicString& type, EventTarget& target)
113 EventInit initializer;
114 initializer.setBubbles(isPrefixed(type));
115 RefPtrWillBeRawPtr<Event> event = Event::create(type, initializer);
116 event->setTarget(&target);
117 return event;
120 const char* Fullscreen::supplementName()
122 return "Fullscreen";
125 Fullscreen& Fullscreen::from(Document& document)
127 Fullscreen* fullscreen = fromIfExists(document);
128 if (!fullscreen) {
129 fullscreen = new Fullscreen(document);
130 WillBeHeapSupplement<Document>::provideTo(document, supplementName(), adoptPtrWillBeNoop(fullscreen));
133 return *fullscreen;
136 Fullscreen* Fullscreen::fromIfExistsSlow(Document& document)
138 return static_cast<Fullscreen*>(WillBeHeapSupplement<Document>::from(document, supplementName()));
141 Element* Fullscreen::fullscreenElementFrom(Document& document)
143 if (Fullscreen* found = fromIfExists(document))
144 return found->fullscreenElement();
145 return 0;
148 Element* Fullscreen::currentFullScreenElementFrom(Document& document)
150 if (Fullscreen* found = fromIfExists(document))
151 return found->webkitCurrentFullScreenElement();
152 return 0;
155 bool Fullscreen::isFullScreen(Document& document)
157 return currentFullScreenElementFrom(document);
160 Fullscreen::Fullscreen(Document& document)
161 : DocumentLifecycleObserver(&document)
162 , m_fullScreenLayoutObject(nullptr)
163 , m_eventQueueTimer(this, &Fullscreen::eventQueueTimerFired)
165 document.setHasFullscreenSupplement();
168 Fullscreen::~Fullscreen()
172 inline Document* Fullscreen::document()
174 return lifecycleContext();
177 void Fullscreen::documentWasDetached()
179 m_eventQueue.clear();
181 if (m_fullScreenLayoutObject)
182 m_fullScreenLayoutObject->destroy();
184 #if ENABLE(OILPAN)
185 m_fullScreenElement = nullptr;
186 m_fullScreenElementStack.clear();
187 #endif
191 #if !ENABLE(OILPAN)
192 void Fullscreen::documentWasDisposed()
194 // NOTE: the context dispose phase is not supported in oilpan. Please
195 // consider using the detach phase instead.
196 m_fullScreenElement = nullptr;
197 m_fullScreenElementStack.clear();
199 #endif
201 void Fullscreen::requestFullscreen(Element& element, RequestType requestType)
203 // It is required by isPrivilegedContext() but isn't
204 // actually used. This could be used later if a warning is shown in the
205 // developer console.
206 String errorMessage;
207 if (document()->isPrivilegedContext(errorMessage)) {
208 UseCounter::count(document(), UseCounter::FullscreenSecureOrigin);
209 } else {
210 UseCounter::count(document(), UseCounter::FullscreenInsecureOrigin);
211 OriginsUsingFeatures::countAnyWorld(*document(), OriginsUsingFeatures::Feature::FullscreenInsecureOrigin);
214 // Ignore this request if the document is not in a live frame.
215 if (!document()->isActive())
216 return;
218 // If |element| is on top of |doc|'s fullscreen element stack, terminate these substeps.
219 if (&element == fullscreenElement())
220 return;
222 do {
223 // 1. If any of the following conditions are true, terminate these steps and queue a task to fire
224 // an event named fullscreenerror with its bubbles attribute set to true on the context object's
225 // node document:
227 // The fullscreen element ready check returns false.
228 if (!fullscreenElementReady(element, requestType))
229 break;
231 // This algorithm is not allowed to show a pop-up:
232 // An algorithm is allowed to show a pop-up if, in the task in which the algorithm is running, either:
233 // - an activation behavior is currently being processed whose click event was trusted, or
234 // - the event listener for a trusted click event is being handled.
235 if (!UserGestureIndicator::processingUserGesture()) {
236 String message = ExceptionMessages::failedToExecute("requestFullScreen",
237 "Element", "API can only be initiated by a user gesture.");
238 document()->executionContext()->addConsoleMessage(
239 ConsoleMessage::create(JSMessageSource, WarningMessageLevel, message));
240 break;
243 // Fullscreen is not supported.
244 if (!fullscreenIsSupported(element.document()))
245 break;
247 // 2. Let doc be element's node document. (i.e. "this")
248 Document* currentDoc = document();
250 // 3. Let docs be all doc's ancestor browsing context's documents (if any) and doc.
251 Deque<Document*> docs;
253 do {
254 docs.prepend(currentDoc);
255 currentDoc = currentDoc->ownerElement() ? &currentDoc->ownerElement()->document() : 0;
256 } while (currentDoc);
258 // 4. For each document in docs, run these substeps:
259 Deque<Document*>::iterator current = docs.begin(), following = docs.begin();
261 do {
262 ++following;
264 // 1. Let following document be the document after document in docs, or null if there is no
265 // such document.
266 Document* currentDoc = *current;
267 Document* followingDoc = following != docs.end() ? *following : 0;
269 // 2. If following document is null, push context object on document's fullscreen element
270 // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
271 // set to true on the document.
272 if (!followingDoc) {
273 from(*currentDoc).pushFullscreenElementStack(element, requestType);
274 enqueueChangeEvent(*currentDoc, requestType);
275 continue;
278 // 3. Otherwise, if document's fullscreen element stack is either empty or its top element
279 // is not following document's browsing context container,
280 Element* topElement = fullscreenElementFrom(*currentDoc);
281 if (!topElement || topElement != followingDoc->ownerElement()) {
282 // ...push following document's browsing context container on document's fullscreen element
283 // stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute
284 // set to true on document.
285 from(*currentDoc).pushFullscreenElementStack(*followingDoc->ownerElement(), requestType);
286 enqueueChangeEvent(*currentDoc, requestType);
287 continue;
290 // 4. Otherwise, do nothing for this document. It stays the same.
291 } while (++current != docs.end());
293 // 5. Return, and run the remaining steps asynchronously.
294 // 6. Optionally, perform some animation.
295 document()->frameHost()->chromeClient().enterFullScreenForElement(&element);
297 // 7. Optionally, display a message indicating how the user can exit displaying the context object fullscreen.
298 return;
299 } while (0);
301 enqueueErrorEvent(element, requestType);
304 void Fullscreen::fullyExitFullscreen(Document& document)
306 // To fully exit fullscreen, run these steps:
308 // 1. Let |doc| be the top-level browsing context's document.
309 Document& doc = document.topDocument();
311 // 2. If |doc|'s fullscreen element stack is empty, terminate these steps.
312 if (!fullscreenElementFrom(doc))
313 return;
315 // 3. Remove elements from |doc|'s fullscreen element stack until only the top element is left.
316 size_t stackSize = from(doc).m_fullScreenElementStack.size();
317 from(doc).m_fullScreenElementStack.remove(0, stackSize - 1);
318 ASSERT(from(doc).m_fullScreenElementStack.size() == 1);
320 // 4. Act as if the exitFullscreen() method was invoked on |doc|.
321 from(doc).exitFullscreen();
324 void Fullscreen::exitFullscreen()
326 // The exitFullscreen() method must run these steps:
328 // 1. Let doc be the context object. (i.e. "this")
329 Document* currentDoc = document();
330 if (!currentDoc->isActive())
331 return;
333 // 2. If doc's fullscreen element stack is empty, terminate these steps.
334 if (m_fullScreenElementStack.isEmpty())
335 return;
337 // 3. Let descendants be all the doc's descendant browsing context's documents with a non-empty fullscreen
338 // element stack (if any), ordered so that the child of the doc is last and the document furthest
339 // away from the doc is first.
340 WillBeHeapDeque<RefPtrWillBeMember<Document>> descendants;
341 for (Frame* descendant = document()->frame() ? document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext()) {
342 if (!descendant->isLocalFrame())
343 continue;
344 ASSERT(toLocalFrame(descendant)->document());
345 if (fullscreenElementFrom(*toLocalFrame(descendant)->document()))
346 descendants.prepend(toLocalFrame(descendant)->document());
349 // 4. For each descendant in descendants, empty descendant's fullscreen element stack, and queue a
350 // task to fire an event named fullscreenchange with its bubbles attribute set to true on descendant.
351 for (auto& descendant : descendants) {
352 ASSERT(descendant);
353 RequestType requestType = from(*descendant).m_fullScreenElementStack.last().second;
354 from(*descendant).clearFullscreenElementStack();
355 enqueueChangeEvent(*descendant, requestType);
358 // 5. While doc is not null, run these substeps:
359 Element* newTop = 0;
360 while (currentDoc) {
361 RequestType requestType = from(*currentDoc).m_fullScreenElementStack.last().second;
363 // 1. Pop the top element of doc's fullscreen element stack.
364 from(*currentDoc).popFullscreenElementStack();
366 // If doc's fullscreen element stack is non-empty and the element now at the top is either
367 // not in a document or its node document is not doc, repeat this substep.
368 newTop = fullscreenElementFrom(*currentDoc);
369 if (newTop && (!newTop->inDocument() || newTop->document() != currentDoc))
370 continue;
372 // 2. Queue a task to fire an event named fullscreenchange with its bubbles attribute set to true
373 // on doc.
374 enqueueChangeEvent(*currentDoc, requestType);
376 // 3. If doc's fullscreen element stack is empty and doc's browsing context has a browsing context
377 // container, set doc to that browsing context container's node document.
378 if (!newTop && currentDoc->ownerElement()) {
379 currentDoc = &currentDoc->ownerElement()->document();
380 continue;
383 // 4. Otherwise, set doc to null.
384 currentDoc = 0;
387 // 6. Return, and run the remaining steps asynchronously.
388 // 7. Optionally, perform some animation.
390 FrameHost* host = document()->frameHost();
392 // Speculative fix for engaget.com/videos per crbug.com/336239.
393 // FIXME: This check is wrong. We ASSERT(document->isActive()) above
394 // so this should be redundant and should be removed!
395 if (!host)
396 return;
398 // Only exit out of full screen window mode if there are no remaining elements in the
399 // full screen stack.
400 if (!newTop) {
401 // FIXME: if the frame exiting fullscreen is not the frame that entered
402 // fullscreen (but a parent frame for example), m_fullScreenElement
403 // might be null. We want to pass an element that is part of the
404 // document so we will pass the documentElement in that case.
405 // This should be fix by exiting fullscreen for a frame instead of an
406 // element, see https://crbug.com/441259
407 host->chromeClient().exitFullScreenForElement(
408 m_fullScreenElement ? m_fullScreenElement.get() : document()->documentElement());
409 return;
412 // Otherwise, notify the chrome of the new full screen element.
413 host->chromeClient().enterFullScreenForElement(newTop);
416 bool Fullscreen::fullscreenEnabled(Document& document)
418 // 4. The fullscreenEnabled attribute must return true if the context object has its
419 // fullscreen enabled flag set and fullscreen is supported, and false otherwise.
421 // Top-level browsing contexts are implied to have their allowFullScreen attribute set.
422 return fullscreenIsAllowedForAllOwners(document) && fullscreenIsSupported(document);
425 void Fullscreen::didEnterFullScreenForElement(Element* element)
427 ASSERT(element);
428 if (!document()->isActive())
429 return;
431 if (m_fullScreenLayoutObject)
432 m_fullScreenLayoutObject->unwrapLayoutObject();
434 m_fullScreenElement = element;
436 // Create a placeholder block for a the full-screen element, to keep the page from reflowing
437 // when the element is removed from the normal flow. Only do this for a LayoutBox, as only
438 // a box will have a frameRect. The placeholder will be created in setFullScreenLayoutObject()
439 // during layout.
440 LayoutObject* layoutObject = m_fullScreenElement->layoutObject();
441 bool shouldCreatePlaceholder = layoutObject && layoutObject->isBox();
442 if (shouldCreatePlaceholder) {
443 m_savedPlaceholderFrameRect = toLayoutBox(layoutObject)->frameRect();
444 m_savedPlaceholderComputedStyle = ComputedStyle::clone(layoutObject->styleRef());
447 if (m_fullScreenElement != document()->documentElement())
448 LayoutFullScreen::wrapLayoutObject(layoutObject, layoutObject ? layoutObject->parent() : 0, document());
450 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
452 // FIXME: This should not call updateStyleIfNeeded.
453 document()->setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::FullScreen));
454 document()->updateLayoutTreeIfNeeded();
456 m_fullScreenElement->didBecomeFullscreenElement();
458 if (document()->frame())
459 document()->frame()->eventHandler().scheduleHoverStateUpdate();
461 m_eventQueueTimer.startOneShot(0, FROM_HERE);
464 void Fullscreen::didExitFullScreenForElement(Element*)
466 if (!m_fullScreenElement)
467 return;
469 if (!document()->isActive())
470 return;
472 m_fullScreenElement->willStopBeingFullscreenElement();
474 m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
476 if (m_fullScreenLayoutObject)
477 m_fullScreenLayoutObject->unwrapLayoutObject();
479 m_fullScreenElement = nullptr;
480 document()->setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::FullScreen));
482 if (document()->frame())
483 document()->frame()->eventHandler().scheduleHoverStateUpdate();
485 // When fullyExitFullscreen is called, we call exitFullscreen on the topDocument(). That means
486 // that the events will be queued there. So if we have no events here, start the timer on the
487 // exiting document.
488 Document* exitingDocument = document();
489 if (m_eventQueue.isEmpty())
490 exitingDocument = &document()->topDocument();
491 ASSERT(exitingDocument);
492 from(*exitingDocument).m_eventQueueTimer.startOneShot(0, FROM_HERE);
495 void Fullscreen::setFullScreenLayoutObject(LayoutFullScreen* layoutObject)
497 if (layoutObject == m_fullScreenLayoutObject)
498 return;
500 if (layoutObject && m_savedPlaceholderComputedStyle) {
501 layoutObject->createPlaceholder(m_savedPlaceholderComputedStyle.release(), m_savedPlaceholderFrameRect);
502 } else if (layoutObject && m_fullScreenLayoutObject && m_fullScreenLayoutObject->placeholder()) {
503 LayoutBlock* placeholder = m_fullScreenLayoutObject->placeholder();
504 layoutObject->createPlaceholder(ComputedStyle::clone(placeholder->styleRef()), placeholder->frameRect());
507 if (m_fullScreenLayoutObject)
508 m_fullScreenLayoutObject->unwrapLayoutObject();
509 ASSERT(!m_fullScreenLayoutObject);
511 m_fullScreenLayoutObject = layoutObject;
514 void Fullscreen::fullScreenLayoutObjectDestroyed()
516 m_fullScreenLayoutObject = nullptr;
519 void Fullscreen::enqueueChangeEvent(Document& document, RequestType requestType)
521 RefPtrWillBeRawPtr<Event> event;
522 if (requestType == UnprefixedRequest) {
523 event = createEvent(EventTypeNames::fullscreenchange, document);
524 } else {
525 ASSERT(document.hasFullscreenSupplement());
526 Fullscreen& fullscreen = from(document);
527 EventTarget* target = fullscreen.fullscreenElement();
528 if (!target)
529 target = fullscreen.webkitCurrentFullScreenElement();
530 if (!target)
531 target = &document;
532 event = createEvent(EventTypeNames::webkitfullscreenchange, *target);
534 m_eventQueue.append(event);
535 // NOTE: The timer is started in didEnterFullScreenForElement/didExitFullScreenForElement.
538 void Fullscreen::enqueueErrorEvent(Element& element, RequestType requestType)
540 RefPtrWillBeRawPtr<Event> event;
541 if (requestType == UnprefixedRequest)
542 event = createEvent(EventTypeNames::fullscreenerror, element.document());
543 else
544 event = createEvent(EventTypeNames::webkitfullscreenerror, element);
545 m_eventQueue.append(event);
546 m_eventQueueTimer.startOneShot(0, FROM_HERE);
549 void Fullscreen::eventQueueTimerFired(Timer<Fullscreen>*)
551 // Since we dispatch events in this function, it's possible that the
552 // document will be detached and GC'd. We protect it here to make sure we
553 // can finish the function successfully.
554 RefPtrWillBeRawPtr<Document> protectDocument(document());
555 WillBeHeapDeque<RefPtrWillBeMember<Event>> eventQueue;
556 m_eventQueue.swap(eventQueue);
558 while (!eventQueue.isEmpty()) {
559 RefPtrWillBeRawPtr<Event> event = eventQueue.takeFirst();
560 Node* target = event->target()->toNode();
562 // If the element was removed from our tree, also message the documentElement.
563 if (!target->inDocument() && document()->documentElement()) {
564 ASSERT(isPrefixed(event->type()));
565 eventQueue.append(createEvent(event->type(), *document()->documentElement()));
568 target->dispatchEvent(event);
572 void Fullscreen::elementRemoved(Element& oldNode)
574 // Whenever the removing steps run with an |oldNode| and |oldNode| is in its node document's
575 // fullscreen element stack, run these steps:
577 // 1. If |oldNode| is at the top of its node document's fullscreen element stack, act as if the
578 // exitFullscreen() method was invoked on that document.
579 if (fullscreenElement() == &oldNode) {
580 exitFullscreen();
581 return;
584 // 2. Otherwise, remove |oldNode| from its node document's fullscreen element stack.
585 for (size_t i = 0; i < m_fullScreenElementStack.size(); ++i) {
586 if (m_fullScreenElementStack[i].first.get() == &oldNode) {
587 m_fullScreenElementStack.remove(i);
588 return;
592 // NOTE: |oldNode| was not in the fullscreen element stack.
595 void Fullscreen::clearFullscreenElementStack()
597 m_fullScreenElementStack.clear();
600 void Fullscreen::popFullscreenElementStack()
602 if (m_fullScreenElementStack.isEmpty())
603 return;
605 m_fullScreenElementStack.removeLast();
608 void Fullscreen::pushFullscreenElementStack(Element& element, RequestType requestType)
610 m_fullScreenElementStack.append(std::make_pair(&element, requestType));
613 DEFINE_TRACE(Fullscreen)
615 #if ENABLE(OILPAN)
616 visitor->trace(m_fullScreenElement);
617 visitor->trace(m_fullScreenElementStack);
618 visitor->trace(m_eventQueue);
619 #endif
620 WillBeHeapSupplement<Document>::trace(visitor);
621 DocumentLifecycleObserver::trace(visitor);
624 } // namespace blink