blob: 5fbb037d3ab3bebe70bf44f6fe91c04be2e1d42a [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* (C) 2006 Alexey Proskuryakov (ap@webkit.org)
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All
* rights reserved.
* Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved.
* (http://www.torchmobile.com/)
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "core/dom/Fullscreen.h"
#include "bindings/core/v8/ConditionalFeatures.h"
#include "core/dom/Document.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/StyleEngine.h"
#include "core/events/Event.h"
#include "core/frame/FrameView.h"
#include "core/frame/HostsUsingFeatures.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLIFrameElement.h"
#include "core/input/EventHandler.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/layout/LayoutBlockFlow.h"
#include "core/layout/LayoutFullScreen.h"
#include "core/layout/api/LayoutFullScreenItem.h"
#include "core/page/ChromeClient.h"
#include "core/svg/SVGSVGElement.h"
#include "platform/ScopedOrientationChangeIndicator.h"
#include "platform/UserGestureIndicator.h"
namespace blink {
namespace {
// https://html.spec.whatwg.org/multipage/embedded-content.html#allowed-to-use
bool allowedToUseFullscreen(const Frame* frame) {
// To determine whether a Document object |document| is allowed to use the
// feature indicated by attribute name |allowattribute|, run these steps:
// 1. If |document| has no browsing context, then return false.
if (!frame)
return false;
if (!RuntimeEnabledFeatures::featurePolicyEnabled()) {
// 2. If |document|'s browsing context is a top-level browsing context, then
// return true.
if (frame->isMainFrame())
return true;
// 3. If |document|'s browsing context has a browsing context container that
// is an iframe element with an |allowattribute| attribute specified, and
// whose node document is allowed to use the feature indicated by
// |allowattribute|, then return true.
if (frame->owner() && frame->owner()->allowFullscreen())
return allowedToUseFullscreen(frame->tree().parent());
// 4. Return false.
return false;
}
// If Feature Policy is enabled, then we need this hack to support it, until
// we have proper support for <iframe allowfullscreen> in FP:
// 1. If FP, by itself, enables fullscreen in this document, then fullscreen
// is allowed.
if (frame->securityContext()->getFeaturePolicy()->isFeatureEnabled(
kFullscreenFeature)) {
return true;
}
// 2. Otherwise, if the embedding frame's document is allowed to use
// fullscreen (either through FP or otherwise), and either:
// a) this is a same-origin embedded document, or
// b) this document's iframe has the allowfullscreen attribute set,
// then fullscreen is allowed.
if (!frame->isMainFrame()) {
if (allowedToUseFullscreen(frame->tree().parent())) {
return (frame->owner() && frame->owner()->allowFullscreen()) ||
frame->tree()
.parent()
->securityContext()
->getSecurityOrigin()
->isSameSchemeHostPortAndSuborigin(
frame->securityContext()->getSecurityOrigin());
}
}
// Otherwise, fullscreen is not allowed. (If we reach here and this is the
// main frame, then fullscreen must have been disabled by FP.)
return false;
}
bool allowedToRequestFullscreen(Document& document) {
// An algorithm is allowed to request fullscreen if one of the following is
// true:
// The algorithm is triggered by a user activation.
if (UserGestureIndicator::utilizeUserGesture())
return true;
// The algorithm is triggered by a user generated orientation change.
if (ScopedOrientationChangeIndicator::processingOrientationChange()) {
UseCounter::count(document,
UseCounter::FullscreenAllowedByOrientationChange);
return true;
}
String message = ExceptionMessages::failedToExecute(
"requestFullscreen", "Element",
"API can only be initiated by a user gesture.");
document.addConsoleMessage(
ConsoleMessage::create(JSMessageSource, WarningMessageLevel, message));
return false;
}
// https://fullscreen.spec.whatwg.org/#fullscreen-is-supported
bool fullscreenIsSupported(const Document& document) {
LocalFrame* frame = document.frame();
if (!frame)
return false;
// Fullscreen is supported if there is no previously-established user
// preference, security risk, or platform limitation.
return !document.settings() || document.settings()->getFullscreenSupported();
}
// https://fullscreen.spec.whatwg.org/#fullscreen-element-ready-check
bool fullscreenElementReady(const Element& element) {
// A fullscreen element ready check for an element |element| returns true if
// all of the following are true, and false otherwise:
// |element| is in a document.
if (!element.isConnected())
return false;
// |element|'s node document is allowed to use the feature indicated by
// attribute name allowfullscreen.
if (!allowedToUseFullscreen(element.document().frame()))
return false;
// |element|'s node document's fullscreen element stack is either empty or its
// top element is an inclusive ancestor of |element|.
if (const Element* topElement =
Fullscreen::fullscreenElementFrom(element.document())) {
if (!topElement->contains(&element))
return false;
}
// |element| has no ancestor element whose local name is iframe and namespace
// is the HTML namespace.
if (Traversal<HTMLIFrameElement>::firstAncestor(element))
return false;
// |element|'s node document's browsing context either has a browsing context
// container and the fullscreen element ready check returns true for
// |element|'s node document's browsing context's browsing context container,
// or it has no browsing context container.
if (const Element* owner = element.document().localOwner()) {
if (!fullscreenElementReady(*owner))
return false;
}
return true;
}
// https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen step 4:
bool requestFullscreenConditionsMet(Element& pending, Document& document) {
// |pending|'s namespace is the HTML namespace or |pending| is an SVG svg or
// MathML math element. Note: MathML is not supported.
if (!pending.isHTMLElement() && !isSVGSVGElement(pending))
return false;
// The fullscreen element ready check for |pending| returns false.
if (!fullscreenElementReady(pending))
return false;
// Fullscreen is supported.
if (!fullscreenIsSupported(document))
return false;
// This algorithm is allowed to request fullscreen.
if (!allowedToRequestFullscreen(document))
return false;
return true;
}
// Walks the frame tree and returns the first local ancestor frame, if any.
LocalFrame* nextLocalAncestor(Frame& frame) {
Frame* parent = frame.tree().parent();
if (!parent)
return nullptr;
if (parent->isLocalFrame())
return toLocalFrame(parent);
return nextLocalAncestor(*parent);
}
// Walks the document's frame tree and returns the document of the first local
// ancestor frame, if any.
Document* nextLocalAncestor(Document& document) {
LocalFrame* frame = document.frame();
if (!frame)
return nullptr;
LocalFrame* next = nextLocalAncestor(*frame);
if (!next)
return nullptr;
DCHECK(next->document());
return next->document();
}
// Helper to walk the ancestor chain and return the Document of the topmost
// local ancestor frame. Note that this is not the same as the topmost frame's
// Document, which might be unavailable in OOPIF scenarios. For example, with
// OOPIFs, when called on the bottom frame's Document in a A-B-C-B hierarchy in
// process B, this will skip remote frame C and return this frame: A-[B]-C-B.
Document& topmostLocalAncestor(Document& document) {
if (Document* next = nextLocalAncestor(document))
return topmostLocalAncestor(*next);
return document;
}
// https://fullscreen.spec.whatwg.org/#collect-documents-to-unfullscreen
HeapVector<Member<Document>> collectDocumentsToUnfullscreen(
Document& doc,
Fullscreen::ExitType exitType) {
DCHECK(Fullscreen::fullscreenElementFrom(doc));
// 1. If |doc|'s top layer consists of more than a single element that has
// its fullscreen flag set, return the empty set.
// TODO(foolip): See TODO in |fullyExitFullscreen()|.
if (exitType != Fullscreen::ExitType::Fully &&
Fullscreen::fullscreenElementStackSizeFrom(doc) > 1)
return HeapVector<Member<Document>>();
// 2. Let |docs| be an ordered set consisting of |doc|.
HeapVector<Member<Document>> docs;
docs.push_back(&doc);
// 3. While |docs|'s last document ...
//
// OOPIF: Skip over remote frames, assuming that they have exactly one element
// in their fullscreen element stacks, thereby erring on the side of exiting
// fullscreen. TODO(alexmos): Deal with nested fullscreen cases, see
// https://crbug.com/617369.
for (Document* document = nextLocalAncestor(doc); document;
document = nextLocalAncestor(*document)) {
// ... has a browsing context container whose node document's top layer
// consists of a single element that has its fullscreen flag set and does
// not have its iframe fullscreen flag set (if any), append that node
// document to |docs|.
// TODO(foolip): Support the iframe fullscreen flag.
// https://crbug.com/644695
if (Fullscreen::fullscreenElementStackSizeFrom(*document) == 1)
docs.push_back(document);
else
break;
}
// 4. Return |docs|.
return docs;
}
// Creates a non-bubbling event with |document| as its target.
Event* createEvent(const AtomicString& type, Document& document) {
DCHECK(type == EventTypeNames::fullscreenchange ||
type == EventTypeNames::fullscreenerror);
Event* event = Event::create(type);
event->setTarget(&document);
return event;
}
// Creates a bubbling event with |element| as its target. If |element| is not
// connected to |document|, then |document| is used as the target instead.
Event* createPrefixedEvent(const AtomicString& type,
Element& element,
Document& document) {
DCHECK(type == EventTypeNames::webkitfullscreenchange ||
type == EventTypeNames::webkitfullscreenerror);
Event* event = Event::createBubble(type);
if (element.isConnected() && element.document() == document)
event->setTarget(&element);
else
event->setTarget(&document);
return event;
}
Event* createChangeEvent(Document& document,
Element& element,
Fullscreen::RequestType requestType) {
if (requestType == Fullscreen::RequestType::Unprefixed)
return createEvent(EventTypeNames::fullscreenchange, document);
return createPrefixedEvent(EventTypeNames::webkitfullscreenchange, element,
document);
}
Event* createErrorEvent(Document& document,
Element& element,
Fullscreen::RequestType requestType) {
if (requestType == Fullscreen::RequestType::Unprefixed)
return createEvent(EventTypeNames::fullscreenerror, document);
return createPrefixedEvent(EventTypeNames::webkitfullscreenerror, element,
document);
}
void dispatchEvents(const HeapVector<Member<Event>>& events) {
for (Event* event : events)
event->target()->dispatchEvent(event);
}
} // anonymous namespace
const char* Fullscreen::supplementName() {
return "Fullscreen";
}
Fullscreen& Fullscreen::from(Document& document) {
Fullscreen* fullscreen = fromIfExists(document);
if (!fullscreen) {
fullscreen = new Fullscreen(document);
Supplement<Document>::provideTo(document, supplementName(), fullscreen);
}
return *fullscreen;
}
Fullscreen* Fullscreen::fromIfExistsSlow(Document& document) {
return static_cast<Fullscreen*>(
Supplement<Document>::from(document, supplementName()));
}
Element* Fullscreen::fullscreenElementFrom(Document& document) {
if (Fullscreen* found = fromIfExists(document))
return found->fullscreenElement();
return nullptr;
}
size_t Fullscreen::fullscreenElementStackSizeFrom(Document& document) {
if (Fullscreen* found = fromIfExists(document))
return found->m_fullscreenElementStack.size();
return 0;
}
Element* Fullscreen::fullscreenElementForBindingFrom(TreeScope& scope) {
Element* element = fullscreenElementFrom(scope.document());
if (!element || !RuntimeEnabledFeatures::fullscreenUnprefixedEnabled())
return element;
// TODO(kochi): Once V0 code is removed, we can use the same logic for
// Document and ShadowRoot.
if (!scope.rootNode().isShadowRoot()) {
// For Shadow DOM V0 compatibility: We allow returning an element in V0
// shadow tree, even though it leaks the Shadow DOM.
if (element->isInV0ShadowTree()) {
UseCounter::count(scope.document(),
UseCounter::DocumentFullscreenElementInV0Shadow);
return element;
}
} else if (!toShadowRoot(scope.rootNode()).isV1()) {
return nullptr;
}
return scope.adjustedElement(*element);
}
Fullscreen::Fullscreen(Document& document)
: Supplement<Document>(document),
ContextLifecycleObserver(&document),
m_fullScreenLayoutObject(nullptr) {
document.setHasFullscreenSupplement();
}
Fullscreen::~Fullscreen() {}
Document* Fullscreen::document() {
return toDocument(lifecycleContext());
}
void Fullscreen::contextDestroyed(ExecutionContext*) {
if (m_fullScreenLayoutObject)
m_fullScreenLayoutObject->destroy();
m_pendingRequests.clear();
m_fullscreenElementStack.clear();
}
// https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen
void Fullscreen::requestFullscreen(Element& pending) {
// TODO(foolip): Make RequestType::Unprefixed the default when the unprefixed
// API is enabled. https://crbug.com/383813
requestFullscreen(pending, RequestType::Prefixed);
}
void Fullscreen::requestFullscreen(Element& pending, RequestType requestType) {
Document& document = pending.document();
// Ignore this call if the document is not in a live frame.
if (!document.isActive() || !document.frame())
return;
bool forCrossProcessDescendant =
requestType == RequestType::PrefixedForCrossProcessDescendant;
// Use counters only need to be incremented in the process of the actual
// fullscreen element.
if (!forCrossProcessDescendant) {
if (document.isSecureContext()) {
UseCounter::count(document, UseCounter::FullscreenSecureOrigin);
} else {
UseCounter::count(document, UseCounter::FullscreenInsecureOrigin);
HostsUsingFeatures::countAnyWorld(
document, HostsUsingFeatures::Feature::FullscreenInsecureHost);
}
}
// 1. Let |pending| be the context object.
// 2. Let |error| be false.
bool error = false;
// 3. Let |promise| be a new promise.
// TODO(foolip): Promises. https://crbug.com/644637
// 4. If any of the following conditions are false, then set |error| to true:
//
// OOPIF: If |requestFullscreen()| was already called in a descendant frame
// and passed the checks, do not check again here.
if (!forCrossProcessDescendant &&
!requestFullscreenConditionsMet(pending, document))
error = true;
// 5. Return |promise|, and run the remaining steps in parallel.
// TODO(foolip): Promises. https://crbug.com/644637
// 6. If |error| is false: Resize pending's top-level browsing context's
// document's viewport's dimensions to match the dimensions of the screen of
// the output device. Optionally display a message how the end user can
// revert this.
if (!error) {
from(document).m_pendingRequests.push_back(
std::make_pair(&pending, requestType));
LocalFrame& frame = *document.frame();
frame.chromeClient().enterFullscreen(frame);
} else {
enqueueTaskForRequest(document, pending, requestType, true);
}
}
void Fullscreen::didEnterFullscreen() {
if (!document())
return;
ElementStack requests;
requests.swap(m_pendingRequests);
for (const ElementStackEntry& request : requests)
enqueueTaskForRequest(*document(), *request.first, request.second, false);
}
void Fullscreen::enqueueTaskForRequest(Document& document,
Element& pending,
RequestType requestType,
bool error) {
// 7. As part of the next animation frame task, run these substeps:
document.enqueueAnimationFrameTask(
WTF::bind(&runTaskForRequest, wrapPersistent(&document),
wrapPersistent(&pending), requestType, error));
}
void Fullscreen::runTaskForRequest(Document* document,
Element* element,
RequestType requestType,
bool error) {
DCHECK(document);
DCHECK(document->isActive());
DCHECK(document->frame());
DCHECK(element);
Document& pendingDoc = *document;
Element& pending = *element;
// TODO(foolip): Spec something like: If |pending|'s node document is not
// |pendingDoc|, then set |error| to true.
// https://github.com/whatwg/fullscreen/issues/33
if (pending.document() != pendingDoc)
error = true;
// 7.1. If either |error| is true or the fullscreen element ready check for
// |pending| returns false, fire an event named fullscreenerror on
// |pending|'s node document, reject |promise| with a TypeError exception,
// and terminate these steps.
// TODO(foolip): Promises. https://crbug.com/644637
if (error || !fullscreenElementReady(pending)) {
Event* event = createErrorEvent(pendingDoc, pending, requestType);
event->target()->dispatchEvent(event);
return;
}
// 7.2. Let |fullscreenElements| be an ordered set initially consisting of
// |pending|.
HeapDeque<Member<Element>> fullscreenElements;
fullscreenElements.append(pending);
// 7.3. While the first element in |fullscreenElements| is in a nested
// browsing context, prepend its browsing context container to
// |fullscreenElements|.
//
// OOPIF: |fullscreenElements| will only contain elements for local ancestors,
// and remote ancestors will be processed in their respective processes. This
// preserves the spec's event firing order for local ancestors, but not for
// remote ancestors. However, that difference shouldn't be observable in
// practice: a fullscreenchange event handler would need to postMessage a
// frame in another renderer process, where the message should be queued up
// and processed after the IPC that dispatches fullscreenchange.
for (Frame* frame = pending.document().frame(); frame;
frame = frame->tree().parent()) {
if (!frame->owner() || !frame->owner()->isLocal())
continue;
Element* element = toHTMLFrameOwnerElement(frame->owner());
fullscreenElements.prepend(element);
}
// 7.4. Let |eventDocs| be an empty list.
// Note: For prefixed requests, the event target is an element, so instead
// let |events| be a list of events to dispatch.
HeapVector<Member<Event>> events;
// 7.5. For each |element| in |fullscreenElements|, in order, run these
// subsubsteps:
for (Element* element : fullscreenElements) {
// 7.5.1. Let |doc| be |element|'s node document.
Document& doc = element->document();
// 7.5.2. If |element| is |doc|'s fullscreen element, terminate these
// subsubsteps.
if (element == fullscreenElementFrom(doc))
continue;
// 7.5.3. Otherwise, append |doc| to |eventDocs|.
events.push_back(createChangeEvent(doc, *element, requestType));
// 7.5.4. If |element| is |pending| and |pending| is an iframe element,
// set |element|'s iframe fullscreen flag.
// TODO(foolip): Support the iframe fullscreen flag.
// https://crbug.com/644695
// 7.5.5. Fullscreen |element| within |doc|.
// TODO(foolip): Merge fullscreen element stack into top layer.
// https://crbug.com/627790
from(doc).pushFullscreenElementStack(*element, requestType);
}
// 7.6. For each |doc| in |eventDocs|, in order, fire an event named
// fullscreenchange on |doc|.
dispatchEvents(events);
// 7.7. Fulfill |promise| with undefined.
// TODO(foolip): Promises. https://crbug.com/644637
}
// https://fullscreen.spec.whatwg.org/#fully-exit-fullscreen
void Fullscreen::fullyExitFullscreen(Document& document) {
// 1. If |document|'s fullscreen element is null, terminate these steps.
// 2. Unfullscreen elements whose fullscreen flag is set, within
// |document|'s top layer, except for |document|'s fullscreen element.
// 3. Exit fullscreen |document|.
// TODO(foolip): Change the spec. To remove elements from |document|'s top
// layer as in step 2 could leave descendant frames in fullscreen. It may work
// to give the "exit fullscreen" algorithm a |fully| flag that's used in the
// animation frame task after exit. Here, retain the old behavior of fully
// exiting fullscreen for the topmost local ancestor:
exitFullscreen(topmostLocalAncestor(document), ExitType::Fully);
}
// https://fullscreen.spec.whatwg.org/#exit-fullscreen
void Fullscreen::exitFullscreen(Document& doc, ExitType exitType) {
if (!doc.isActive() || !doc.frame())
return;
// 1. Let |promise| be a new promise.
// 2. If |doc|'s fullscreen element is null, reject |promise| with a
// TypeError exception, and return |promise|.
// TODO(foolip): Promises. https://crbug.com/644637
if (!fullscreenElementFrom(doc))
return;
// 3. Let |resize| be false.
bool resize = false;
// 4. Let |docs| be the result of collecting documents to unfullscreen given
// |doc|.
HeapVector<Member<Document>> docs =
collectDocumentsToUnfullscreen(doc, exitType);
// 5. Let |topLevelDoc| be |doc|'s top-level browsing context's document.
//
// OOPIF: Let |topLevelDoc| be the topmost local ancestor instead. If the main
// frame is in another process, we will still fully exit fullscreen even
// though that's wrong if the main frame was in nested fullscreen.
// TODO(alexmos): Deal with nested fullscreen cases, see
// https://crbug.com/617369.
Document& topLevelDoc = topmostLocalAncestor(doc);
// 6. If |topLevelDoc| is in |docs|, set |resize| to true.
if (!docs.isEmpty() && docs.back() == &topLevelDoc)
resize = true;
// 7. Return |promise|, and run the remaining steps in parallel.
// TODO(foolip): Promises. https://crbug.com/644637
// Note: |ExitType::Fully| is only used together with the topmost local
// ancestor in |fullyExitFullscreen()|, and so implies that |resize| is true.
// This would change if matching the spec for "fully exit fullscreen".
if (exitType == ExitType::Fully)
DCHECK(resize);
// 8. If |resize| is true, resize |topLevelDoc|'s viewport to its "normal"
// dimensions.
if (resize) {
LocalFrame& frame = *doc.frame();
frame.chromeClient().exitFullscreen(frame);
} else {
enqueueTaskForExit(doc, exitType);
}
}
void Fullscreen::didExitFullscreen() {
if (!document())
return;
DCHECK_EQ(document(), &topmostLocalAncestor(*document()));
enqueueTaskForExit(*document(), ExitType::Fully);
}
void Fullscreen::enqueueTaskForExit(Document& document, ExitType exitType) {
// 9. As part of the next animation frame task, run these substeps:
document.enqueueAnimationFrameTask(
WTF::bind(&runTaskForExit, wrapPersistent(&document), exitType));
}
void Fullscreen::runTaskForExit(Document* document, ExitType exitType) {
DCHECK(document);
DCHECK(document->isActive());
DCHECK(document->frame());
Document& doc = *document;
if (!fullscreenElementFrom(doc))
return;
// 9.1. Let |exitDocs| be the result of collecting documents to unfullscreen
// given |doc|.
// 9.2. If |resize| is true and |topLevelDoc| is not in |exitDocs|, fully
// exit fullscreen |topLevelDoc|, reject promise with a TypeError exception,
// and terminate these steps.
// TODO(foolip): See TODO in |fullyExitFullscreen()|. Instead of using "fully
// exit fullscreen" in step 9.2 (which is async), give "exit fullscreen" a
// |fully| flag which is always true if |resize| was true.
HeapVector<Member<Document>> exitDocs =
collectDocumentsToUnfullscreen(doc, exitType);
// 9.3. If |exitDocs| is the empty set, append |doc| to |exitDocs|.
if (exitDocs.isEmpty())
exitDocs.push_back(&doc);
// 9.4. If |exitDocs|'s last document has a browsing context container,
// append that browsing context container's node document to |exitDocs|.
//
// OOPIF: Skip over remote frames, assuming that they have exactly one element
// in their fullscreen element stacks, thereby erring on the side of exiting
// fullscreen. TODO(alexmos): Deal with nested fullscreen cases, see
// https://crbug.com/617369.
if (Document* document = nextLocalAncestor(*exitDocs.back()))
exitDocs.push_back(document);
// 9.5. Let |descendantDocs| be an ordered set consisting of |doc|'s
// descendant browsing contexts' documents whose fullscreen element is
// non-null, if any, in *reverse* tree order.
HeapDeque<Member<Document>> descendantDocs;
for (Frame* descendant = doc.frame()->tree().firstChild(); descendant;
descendant = descendant->tree().traverseNext(doc.frame())) {
if (!descendant->isLocalFrame())
continue;
DCHECK(toLocalFrame(descendant)->document());
if (fullscreenElementFrom(*toLocalFrame(descendant)->document()))
descendantDocs.prepend(toLocalFrame(descendant)->document());
}
// Note: For prefixed requests, the event target is an element, so let
// |events| be a list of events to dispatch.
HeapVector<Member<Event>> events;
// 9.6. For each |descendantDoc| in |descendantDocs|, in order, unfullscreen
// |descendantDoc|.
for (Document* descendantDoc : descendantDocs) {
Fullscreen& fullscreen = from(*descendantDoc);
ElementStack& stack = fullscreen.m_fullscreenElementStack;
DCHECK(!stack.isEmpty());
events.push_back(createChangeEvent(*descendantDoc, *stack.back().first,
stack.back().second));
while (!stack.isEmpty())
fullscreen.popFullscreenElementStack();
}
// 9.7. For each |exitDoc| in |exitDocs|, in order, unfullscreen |exitDoc|'s
// fullscreen element.
for (Document* exitDoc : exitDocs) {
Fullscreen& fullscreen = from(*exitDoc);
ElementStack& stack = fullscreen.m_fullscreenElementStack;
DCHECK(!stack.isEmpty());
events.push_back(
createChangeEvent(*exitDoc, *stack.back().first, stack.back().second));
fullscreen.popFullscreenElementStack();
// TODO(foolip): See TODO in |fullyExitFullscreen()|.
if (exitDoc == &doc && exitType == ExitType::Fully) {
while (!stack.isEmpty())
fullscreen.popFullscreenElementStack();
}
}
// 9.8. For each |descendantDoc| in |descendantDocs|, in order, fire an
// event named fullscreenchange on |descendantDoc|.
// 9.9. For each |exitDoc| in |exitDocs|, in order, fire an event named
// fullscreenchange on |exitDoc|.
dispatchEvents(events);
// 9.10. Fulfill |promise| with undefined.
// TODO(foolip): Promises. https://crbug.com/644637
}
// https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled
bool Fullscreen::fullscreenEnabled(Document& document) {
// The fullscreenEnabled attribute's getter must return true if the context
// object is allowed to use the feature indicated by attribute name
// allowfullscreen and fullscreen is supported, and false otherwise.
return allowedToUseFullscreen(document.frame()) &&
fullscreenIsSupported(document);
}
void Fullscreen::setFullScreenLayoutObject(LayoutFullScreen* layoutObject) {
if (layoutObject == m_fullScreenLayoutObject)
return;
if (layoutObject && m_savedPlaceholderComputedStyle) {
layoutObject->createPlaceholder(std::move(m_savedPlaceholderComputedStyle),
m_savedPlaceholderFrameRect);
} else if (layoutObject && m_fullScreenLayoutObject &&
m_fullScreenLayoutObject->placeholder()) {
LayoutBlockFlow* placeholder = m_fullScreenLayoutObject->placeholder();
layoutObject->createPlaceholder(
ComputedStyle::clone(placeholder->styleRef()),
placeholder->frameRect());
}
if (m_fullScreenLayoutObject)
m_fullScreenLayoutObject->unwrapLayoutObject();
DCHECK(!m_fullScreenLayoutObject);
m_fullScreenLayoutObject = layoutObject;
}
void Fullscreen::fullScreenLayoutObjectDestroyed() {
m_fullScreenLayoutObject = nullptr;
}
void Fullscreen::elementRemoved(Element& oldNode) {
DCHECK_EQ(document(), &oldNode.document());
// Whenever the removing steps run with an |oldNode| and |oldNode| is in its
// node document's fullscreen element stack, run these steps:
// 1. If |oldNode| is at the top of its node document's fullscreen element
// stack, act as if the exitFullscreen() method was invoked on that document.
if (fullscreenElement() == &oldNode) {
exitFullscreen(oldNode.document());
return;
}
// 2. Otherwise, remove |oldNode| from its node document's fullscreen element
// stack.
for (size_t i = 0; i < m_fullscreenElementStack.size(); ++i) {
if (m_fullscreenElementStack[i].first.get() == &oldNode) {
m_fullscreenElementStack.remove(i);
return;
}
}
// NOTE: |oldNode| was not in the fullscreen element stack.
}
void Fullscreen::popFullscreenElementStack() {
DCHECK(!m_fullscreenElementStack.isEmpty());
Element* previousElement = fullscreenElement();
m_fullscreenElementStack.pop_back();
// Note: |requestType| is only used if |fullscreenElement()| is non-null.
RequestType requestType = m_fullscreenElementStack.isEmpty()
? RequestType::Unprefixed
: m_fullscreenElementStack.back().second;
fullscreenElementChanged(previousElement, fullscreenElement(), requestType);
}
void Fullscreen::pushFullscreenElementStack(Element& element,
RequestType requestType) {
Element* previousElement = fullscreenElement();
m_fullscreenElementStack.push_back(std::make_pair(&element, requestType));
fullscreenElementChanged(previousElement, &element, requestType);
}
void Fullscreen::fullscreenElementChanged(Element* fromElement,
Element* toElement,
RequestType toRequestType) {
DCHECK_NE(fromElement, toElement);
if (!document())
return;
document()->styleEngine().ensureUAStyleForFullscreen();
if (m_fullScreenLayoutObject)
m_fullScreenLayoutObject->unwrapLayoutObject();
DCHECK(!m_fullScreenLayoutObject);
if (fromElement) {
DCHECK_NE(fromElement, fullscreenElement());
fromElement->pseudoStateChanged(CSSSelector::PseudoFullScreen);
fromElement->setContainsFullScreenElement(false);
fromElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(
false);
}
if (toElement) {
DCHECK_EQ(toElement, fullscreenElement());
toElement->pseudoStateChanged(CSSSelector::PseudoFullScreen);
// OOPIF: For RequestType::PrefixedForCrossProcessDescendant, |toElement| is
// the iframe element for the out-of-process frame that contains the
// fullscreen element. Hence, it must match :-webkit-full-screen-ancestor.
if (toRequestType == RequestType::PrefixedForCrossProcessDescendant) {
DCHECK(isHTMLIFrameElement(toElement));
toElement->setContainsFullScreenElement(true);
}
toElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(
true);
// Create a placeholder block for the fullscreen element, to keep the page
// from reflowing when the element is removed from the normal flow. Only do
// this for a LayoutBox, as only a box will have a frameRect. The
// placeholder will be created in setFullScreenLayoutObject() during layout.
LayoutObject* layoutObject = toElement->layoutObject();
bool shouldCreatePlaceholder = layoutObject && layoutObject->isBox();
if (shouldCreatePlaceholder) {
m_savedPlaceholderFrameRect = toLayoutBox(layoutObject)->frameRect();
m_savedPlaceholderComputedStyle =
ComputedStyle::clone(layoutObject->styleRef());
}
if (toElement != document()->documentElement()) {
LayoutFullScreen::wrapLayoutObject(
layoutObject, layoutObject ? layoutObject->parent() : 0, document());
}
}
if (LocalFrame* frame = document()->frame()) {
// TODO(foolip): Synchronize hover state changes with animation frames.
// https://crbug.com/668758
frame->eventHandler().scheduleHoverStateUpdate();
frame->chromeClient().fullscreenElementChanged(fromElement, toElement);
if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() &&
!RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
// Fullscreen status affects scroll paint properties through
// FrameView::userInputScrollable().
if (FrameView* frameView = frame->view())
frameView->setNeedsPaintPropertyUpdate();
}
}
// TODO(foolip): This should not call updateStyleAndLayoutTree.
document()->updateStyleAndLayoutTree();
}
DEFINE_TRACE(Fullscreen) {
visitor->trace(m_pendingRequests);
visitor->trace(m_fullscreenElementStack);
Supplement<Document>::trace(visitor);
ContextLifecycleObserver::trace(visitor);
}
} // namespace blink