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.
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"
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
))
57 if (!owner
->hasAttribute(allowfullscreenAttr
))
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())
79 // |element|'s node document's fullscreen enabled flag is set.
80 if (!fullscreenIsAllowedForAllOwners(element
.document()))
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
))
90 // |element| has no ancestor element whose local name is iframe and namespace is the HTML
92 if (Traversal
<HTMLIFrameElement
>::firstAncestor(element
))
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
))
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
);
120 const char* Fullscreen::supplementName()
125 Fullscreen
& Fullscreen::from(Document
& document
)
127 Fullscreen
* fullscreen
= fromIfExists(document
);
129 fullscreen
= new Fullscreen(document
);
130 WillBeHeapSupplement
<Document
>::provideTo(document
, supplementName(), adoptPtrWillBeNoop(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();
148 Element
* Fullscreen::currentFullScreenElementFrom(Document
& document
)
150 if (Fullscreen
* found
= fromIfExists(document
))
151 return found
->webkitCurrentFullScreenElement();
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();
185 m_fullScreenElement
= nullptr;
186 m_fullScreenElementStack
.clear();
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();
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.
207 if (document()->isPrivilegedContext(errorMessage
)) {
208 UseCounter::count(document(), UseCounter::FullscreenSecureOrigin
);
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())
218 // If |element| is on top of |doc|'s fullscreen element stack, terminate these substeps.
219 if (&element
== fullscreenElement())
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
227 // The fullscreen element ready check returns false.
228 if (!fullscreenElementReady(element
, requestType
))
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
));
243 // Fullscreen is not supported.
244 if (!fullscreenIsSupported(element
.document()))
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
;
254 docs
.prepend(currentDoc
);
255 currentDoc
= currentDoc
->ownerElement() ? ¤tDoc
->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();
264 // 1. Let following document be the document after document in docs, or null if there is no
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.
273 from(*currentDoc
).pushFullscreenElementStack(element
, requestType
);
274 enqueueChangeEvent(*currentDoc
, requestType
);
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
);
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.
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
))
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())
333 // 2. If doc's fullscreen element stack is empty, terminate these steps.
334 if (m_fullScreenElementStack
.isEmpty())
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())
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
) {
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:
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
))
372 // 2. Queue a task to fire an event named fullscreenchange with its bubbles attribute set to true
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
= ¤tDoc
->ownerElement()->document();
383 // 4. Otherwise, set doc to null.
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!
398 // Only exit out of full screen window mode if there are no remaining elements in the
399 // full screen stack.
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());
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
)
428 if (!document()->isActive())
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()
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
)
469 if (!document()->isActive())
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
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
)
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
);
525 ASSERT(document
.hasFullscreenSupplement());
526 Fullscreen
& fullscreen
= from(document
);
527 EventTarget
* target
= fullscreen
.fullscreenElement();
529 target
= fullscreen
.webkitCurrentFullScreenElement();
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());
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
) {
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
);
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())
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
)
616 visitor
->trace(m_fullScreenElement
);
617 visitor
->trace(m_fullScreenElementStack
);
618 visitor
->trace(m_eventQueue
);
620 WillBeHeapSupplement
<Document
>::trace(visitor
);
621 DocumentLifecycleObserver::trace(visitor
);