Skip to content
Permalink
Browse files
Make fullscreen API use the top layer
https://bugs.webkit.org/show_bug.cgi?id=84798
rdar://11313423

Reviewed by Darin Adler.

The fullscreen API should use the top layer instead of the fullscreen stack. Some notes:
- Pushing to the top layer causes visual changes, so we need to update the DOM methods to follow spec order to prevent unintended effects.
Visual changes can't happen before `willEnterFullscreen` and can't happen before `didExitFullscreen` when fully entering/exiting fullscreen.

- Since top layer handles visual z-ordering, we can remove old style hacks of clobbering the ancestor chain with special styling, and the associated
containsFullscreenElement node flag.

Spec: https://fullscreen.spec.whatwg.org

* LayoutTests/fullscreen/full-screen-enter-while-exiting-expected.txt:
Events are now scheduled and dispatched in didExitFullscreen instead of exitFullscreen(), hence the order change.

* Source/WebCore/css/CSSSelector.cpp:
(WebCore::CSSSelector::selectorText const):
* Source/WebCore/css/CSSSelector.h:
* Source/WebCore/css/SelectorPseudoClassAndCompatibilityElementMap.in:
* Source/WebCore/cssjit/SelectorCompiler.cpp:
(WebCore::SelectorCompiler::addPseudoClassType):
Remove temporary backdrop workaround that was landed in https://commits.webkit.org/256226@main .

* Source/WebCore/css/SelectorChecker.cpp:
(WebCore::SelectorChecker::checkOne const):
* Source/WebCore/css/SelectorCheckerTestFunctions.h:
(WebCore::matchesFullScreenPseudoClass):
(WebCore::matchesFullScreenAncestorPseudoClass):
Rewrite selector checker functions to account for removal of containsFullscreenElement flag.
Since :-webkit-fullscreen-ancestor main usage was for the UA stylesheet, we may be able to remove it in a followup clean up.

(WebCore::matchesFullScreenParentPseudoClass): Deleted. (part of the backdrop workaround)
* Source/WebCore/css/fullscreen.css:
(#if defined(ENABLE_FULLSCREEN_API) && ENABLE_FULLSCREEN_API):
(:root:-webkit-full-screen-document:not(:-webkit-full-screen)): Only keep overflow: hidden; since top layer does not make the root element unscrollable.
(iframe:-webkit-full-screen): Match the spec.
(:not(:root):-webkit-full-screen::backdrop): Match the spec by making the fullscreen backdrops black.
(:-webkit-full-screen): Deleted. (not needed with top layer)
(:root:-webkit-full-screen-document:not(:-webkit-full-screen), :root:-webkit-full-screen-ancestor): Deleted.
(:-webkit-full-screen-ancestor:not(iframe)): Deleted. (not needed with top layer)
(video:-webkit-full-screen, audio:-webkit-full-screen): Deleted. (to match the spec)
(:-webkit-full-screen-parent::before): Deleted. (fake backdrop workaround)
* Source/WebCore/dom/Document.cpp:
(WebCore::Document::nodeChildrenWillBeRemoved):
(WebCore::Document::nodeWillBeRemoved):
Move element removal handling to `Element::removedFromAncestor()`

* Source/WebCore/dom/Element.cpp:
(WebCore::Element::insertedIntoAncestor):
(WebCore::Element::removedFromAncestor):
(WebCore::Element::setFullscreenFlag):
(WebCore::Element::setIframeFullscreenFlag):
(WebCore::parentCrossingFrameBoundaries): Deleted.
(WebCore::Element::setContainsFullScreenElement): Deleted.
(WebCore::Element::setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries): Deleted.
Removed the containsFullscreenElement flag and replace it with fullscreen flag and iframe fullscreen flag.

* Source/WebCore/dom/Element.h: Ditto
(WebCore::Element::hasFullscreenFlag const):
(WebCore::Element::hasIframeFullscreenFlag const):
(WebCore::Element::containsFullScreenElement const): Deleted.

* Source/WebCore/dom/FullscreenManager.cpp:
(WebCore::FullscreenManager::fullscreenElement const): Rewrite it to iterate through top layer instead of fullscreen stack.
(WebCore::FullscreenManager::requestFullscreenForElement): Removed all fullscreen stack handling. Top layer handling is now in `willEnterFullscreen` since it performs visual changes.
(WebCore::FullscreenManager::cancelFullscreen): Basically exitFullscreen() but always exits everything.
(WebCore::documentsToUnfullscreen): "collecting documents to unfullscreen" algorithm from spec
(WebCore::FullscreenManager::exitFullscreen): Steps 1 - 8 of exit fullscreen algorithm from spec
(WebCore::FullscreenManager::finishExitFullscreen): Steps 12 - 15 of exit fullscreen algorithm from spec
(WebCore::FullscreenManager::willEnterFullscreen): Push documents to top layer, add fullscreen flag & emit events (Step 12 of request fullscreen in spec)
(WebCore::FullscreenManager::didExitFullscreen): Everything else that is needed when fully exiting fullscreen.
(WebCore::FullscreenManager::exitRemovedFullscreenElementIfNeeded): "removing steps" for element from spec (except for top layer removal which is already done later on in Element::removedFromAncestor)
(WebCore::FullscreenManager::isSimpleFullscreenDocument const): "Simple fullscreen document" as defined by the spec, e.g. a document with only one fullscreen element in the top layer.

(WebCore::FullscreenManager::adjustFullscreenElementOnNodeRemoval): Deleted. Replaced by exitRemovedFullscreenElementIfNeeded called from Element::removedFromAncestor().

(WebCore::FullscreenManager::clearFullscreenElementStack): Deleted.
(WebCore::FullscreenManager::popFullscreenElementStack): Deleted.
(WebCore::FullscreenManager::pushFullscreenElementStack): Deleted.
(WebCore::FullscreenManager::clear):
Remove fullscreen stack remainders.

* Source/WebCore/dom/FullscreenManager.h:

* Source/WebCore/dom/Node.h:
Add IsFullscreen/IsIframeFullscreen flags, remove ContainsFullScreenElement.
Shuffle flags around, since Bit 31 is actually not usable (causes integer overflow when using it).

Canonical link: https://commits.webkit.org/257456@main
  • Loading branch information
nt1m committed Dec 7, 2022
1 parent df0e511 commit c96dd4f574c01a761f875ce4222725c44693f2b1
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 308 deletions.
@@ -8,8 +8,8 @@ RUN(document.webkitExitFullscreen())
RUN(internals.webkitWillExitFullScreenForElement(target1))
Attempt to enter fullscreen with target2
RUN(target2.webkitRequestFullScreen())
EVENT(webkitfullscreenchange)
EVENT(webkitfullscreenerror)
RUN(internals.webkitDidExitFullScreenForElement(target1))
EVENT(webkitfullscreenchange)
END OF TEST

@@ -475,9 +475,6 @@ String CSSSelector::selectorText(StringView separator, StringView rightSide) con
case CSSSelector::PseudoClassFullScreenControlsHidden:
builder.append(":-webkit-full-screen-controls-hidden");
break;
case CSSSelector::PseudoClassFullScreenParent:
builder.append(":-webkit-full-screen-parent");
break;
#endif
#if ENABLE(PICTURE_IN_PICTURE_API)
case CSSSelector::PseudoClassPictureInPicture:
@@ -160,7 +160,6 @@ struct PossiblyQuotedIdentifier {
PseudoClassFullScreenAncestor,
PseudoClassAnimatingFullScreenTransition,
PseudoClassFullScreenControlsHidden,
PseudoClassFullScreenParent,
#endif
#if ENABLE(PICTURE_IN_PICTURE_API)
PseudoClassPictureInPicture,
@@ -1046,8 +1046,6 @@ bool SelectorChecker::checkOne(CheckingContext& checkingContext, const LocalCont
return matchesFullScreenAnimatingFullScreenTransitionPseudoClass(element);
case CSSSelector::PseudoClassFullScreenAncestor:
return matchesFullScreenAncestorPseudoClass(element);
case CSSSelector::PseudoClassFullScreenParent:
return matchesFullScreenParentPseudoClass(element);
case CSSSelector::PseudoClassFullScreenDocument:
return matchesFullScreenDocumentPseudoClass(element);
case CSSSelector::PseudoClassFullScreenControlsHidden:
@@ -408,7 +408,7 @@ ALWAYS_INLINE bool matchesFullScreenPseudoClass(const Element& element)
// element is an element in the document, the 'full-screen' pseudoclass applies to
// that element. Also, an <iframe>, <object> or <embed> element whose child browsing
// context's Document is in the fullscreen state has the 'full-screen' pseudoclass applied.
if (is<HTMLFrameElementBase>(element) && element.containsFullScreenElement())
if (is<HTMLFrameElementBase>(element) && element.hasFullscreenFlag())
return true;
if (!element.document().fullscreenManager().isFullscreen())
return false;
@@ -422,17 +422,10 @@ ALWAYS_INLINE bool matchesFullScreenAnimatingFullScreenTransitionPseudoClass(con
return element.document().fullscreenManager().isAnimatingFullscreen();
}

/* FIXME: Remove when we use top layer, since this won't be needed (webkit.org/b/84798). */
ALWAYS_INLINE bool matchesFullScreenParentPseudoClass(const Element& element)
{
if (!element.document().fullscreenManager().isFullscreen())
return false;
return &element == element.document().fullscreenManager().currentFullscreenElement()->parentElement();
}

ALWAYS_INLINE bool matchesFullScreenAncestorPseudoClass(const Element& element)
{
return element.containsFullScreenElement();
auto* currentFullscreenElement = element.document().fullscreenManager().currentFullscreenElement();
return currentFullscreenElement && currentFullscreenElement->isDescendantOrShadowDescendantOf(element);
}

ALWAYS_INLINE bool matchesFullScreenDocumentPseudoClass(const Element& element)
@@ -79,7 +79,6 @@ window-inactive
-webkit-full-screen-ancestor
-webkit-full-screen-document
-webkit-full-screen-controls-hidden
-webkit-full-screen-parent
#endif

#if ENABLE(PICTURE_IN_PICTURE_API)
@@ -24,6 +24,8 @@

#if defined(ENABLE_FULLSCREEN_API) && ENABLE_FULLSCREEN_API

/* https://fullscreen.spec.whatwg.org/#user-agent-level-style-sheet-defaults */

:not(:root):-webkit-full-screen {
position: fixed !important;
inset: 0 !important;
@@ -41,43 +43,10 @@
object-fit:contain;
}

:-webkit-full-screen {
z-index: 2147483647 !important;
width: 100%;
height: 100%;
}

:root:-webkit-full-screen-document:not(:-webkit-full-screen), :root:-webkit-full-screen-ancestor {
:root:-webkit-full-screen-document:not(:-webkit-full-screen) {
overflow: hidden !important;
}

:-webkit-full-screen-ancestor:not(iframe) {
z-index: auto !important;
position: static !important;
opacity: 1 !important;
transform: none !important;
mask: none !important;
clip: none !important;
filter: none !important;
transition: none !important;
-webkit-box-reflect: none !important;
perspective: none !important;
transform-style: flat !important;
overflow: visible !important;
contain: none !important;
}

video:-webkit-full-screen, audio:-webkit-full-screen {
background-color: transparent !important;
position: static !important;
margin: 0 !important;
height: 100% !important;
width: 100% !important;
flex: 1 !important;
display: block !important;
transition: none !important;
}

:-webkit-full-screen video,
video:-webkit-full-screen {
-webkit-cursor-visibility: auto-hide;
@@ -90,29 +59,12 @@ img:-webkit-full-screen {
}

iframe:-webkit-full-screen {
margin: 0 !important;
border: none !important;
padding: 0 !important;
border: 0 !important;
position: fixed !important;
height: 100% !important;
width: 100% !important;
left: 0 !important;
top: 0 !important;
max-width: none !important;
max-height: none !important;
}

/* FIXME: Use top layer backdrop (webkit.org/b/84798). */
:-webkit-full-screen-parent::before {
content: "" !important;
position: fixed !important;
background: black !important;
z-index: 2147483647 !important;
width: 100vw !important;
height: 100vh !important;
margin: 0 !important;
inset: 0 !important;
box-sizing: border-box !important;
:not(:root):-webkit-full-screen::backdrop {
background: black;
}

#endif
@@ -106,7 +106,6 @@ static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(operationMatchesLangPseudo
#if ENABLE(FULLSCREEN_API)
static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(operationMatchesFullScreenPseudoClass, bool, (const Element&));
static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(operationMatchesFullScreenDocumentPseudoClass, bool, (const Element&));
static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(operationMatchesFullScreenParentPseudoClass, bool, (const Element&));
static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(operationMatchesFullScreenAncestorPseudoClass, bool, (const Element&));
static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(operationMatchesFullScreenAnimatingFullScreenTransitionPseudoClass, bool, (const Element&));
static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(operationMatchesFullScreenControlsHiddenPseudoClass, bool, (const Element&));
@@ -727,11 +726,6 @@ JSC_DEFINE_JIT_OPERATION(operationMatchesFullScreenDocumentPseudoClass, bool, (c
return matchesFullScreenDocumentPseudoClass(element);
}

JSC_DEFINE_JIT_OPERATION(operationMatchesFullScreenParentPseudoClass, bool, (const Element& element))
{
return matchesFullScreenParentPseudoClass(element);
}

JSC_DEFINE_JIT_OPERATION(operationMatchesFullScreenAncestorPseudoClass, bool, (const Element& element))
{
return matchesFullScreenAncestorPseudoClass(element);
@@ -908,9 +902,6 @@ static inline FunctionType addPseudoClassType(const CSSSelector& selector, Selec
case CSSSelector::PseudoClassFullScreenDocument:
fragment.unoptimizedPseudoClasses.append(CodePtr<JSC::OperationPtrTag>(operationMatchesFullScreenDocumentPseudoClass));
return FunctionType::SimpleSelectorChecker;
case CSSSelector::PseudoClassFullScreenParent:
fragment.unoptimizedPseudoClasses.append(CodePtr<JSC::OperationPtrTag>(operationMatchesFullScreenParentPseudoClass));
return FunctionType::SimpleSelectorChecker;
case CSSSelector::PseudoClassFullScreenAncestor:
fragment.unoptimizedPseudoClasses.append(CodePtr<JSC::OperationPtrTag>(operationMatchesFullScreenAncestorPseudoClass));
return FunctionType::SimpleSelectorChecker;
@@ -5021,10 +5021,6 @@ void Document::nodeChildrenWillBeRemoved(ContainerNode& container)
adjustFocusedNodeOnNodeRemoval(container, NodeRemoval::ChildrenOfNode);
adjustFocusNavigationNodeOnNodeRemoval(container, NodeRemoval::ChildrenOfNode);

#if ENABLE(FULLSCREEN_API)
m_fullscreenManager->adjustFullscreenElementOnNodeRemoval(container, NodeRemoval::ChildrenOfNode);
#endif

for (auto* range : m_ranges)
range->nodeChildrenWillBeRemoved(container);

@@ -5054,10 +5050,6 @@ void Document::nodeWillBeRemoved(Node& node)
adjustFocusedNodeOnNodeRemoval(node);
adjustFocusNavigationNodeOnNodeRemoval(node);

#if ENABLE(FULLSCREEN_API)
m_fullscreenManager->adjustFullscreenElementOnNodeRemoval(node, NodeRemoval::Node);
#endif

for (auto* it : m_nodeIterators)
it->nodeWillBeRemoved(node);

@@ -2503,11 +2503,6 @@ Node::InsertedIntoAncestorResult Element::insertedIntoAncestor(InsertionType ins
{
ContainerNode::insertedIntoAncestor(insertionType, parentOfInsertedTree);

#if ENABLE(FULLSCREEN_API)
if (containsFullScreenElement() && parentElement() && !parentElement()->containsFullScreenElement())
setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
#endif

if (parentOfInsertedTree.isInTreeScope()) {
bool becomeConnected = insertionType.connectedToDocument;
auto* newScope = &parentOfInsertedTree.treeScope();
@@ -2558,11 +2553,6 @@ Node::InsertedIntoAncestorResult Element::insertedIntoAncestor(InsertionType ins

void Element::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
{
#if ENABLE(FULLSCREEN_API)
if (containsFullScreenElement())
setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
#endif

if (auto* page = document().page()) {
#if ENABLE(POINTER_LOCK)
page->pointerLockController().elementWasRemoved(*this);
@@ -2614,6 +2604,10 @@ void Element::removedFromAncestor(RemovalType removalType, ContainerNode& oldPar

ContainerNode::removedFromAncestor(removalType, oldParentOfRemovedTree);

#if ENABLE(FULLSCREEN_API)
document().fullscreenManager().exitRemovedFullscreenElementIfNeeded(*this);
#endif

if (UNLIKELY(hasRareData()) && !elementRareData()->effectiveLang().isNull()) {
if (auto* parent = parentOrShadowHostElement(); langFromAttribute().isNull()
&& !(parent && UNLIKELY(parent->hasRareData()) && !parent->elementRareData()->effectiveLang().isNull()))
@@ -4162,14 +4156,6 @@ bool Element::childShouldCreateRenderer(const Node& child) const

#if ENABLE(FULLSCREEN_API)

static Element* parentCrossingFrameBoundaries(const Element* element)
{
ASSERT(element);
if (auto* parent = element->parentElementInComposedTree())
return parent;
return element->document().ownerElement();
}

void Element::webkitRequestFullscreen()
{
requestFullscreen({ }, nullptr);
@@ -4181,20 +4167,22 @@ void Element::requestFullscreen(FullscreenOptions&&, RefPtr<DeferredPromise>&& p
document().fullscreenManager().requestFullscreenForElement(*this, WTFMove(promise), FullscreenManager::EnforceIFrameAllowFullscreenRequirement);
}

void Element::setContainsFullScreenElement(bool flag)
void Element::setFullscreenFlag(bool flag)
{
if (flag)
setNodeFlag(NodeFlag::ContainsFullScreenElement);
setNodeFlag(NodeFlag::IsFullscreen);
else
clearNodeFlag(NodeFlag::ContainsFullScreenElement);
invalidateStyleAndLayerComposition();
clearNodeFlag(NodeFlag::IsFullscreen);

clearNodeFlag(NodeFlag::IsIFrameFullscreen);
}

void Element::setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(bool flag)
void Element::setIFrameFullscreenFlag(bool flag)
{
Element* element = this;
while ((element = parentCrossingFrameBoundaries(element)))
element->setContainsFullScreenElement(flag);
if (flag)
setNodeFlag(NodeFlag::IsIFrameFullscreen);
else
clearNodeFlag(NodeFlag::IsIFrameFullscreen);
}

#endif
@@ -566,9 +566,10 @@ class Element : public ContainerNode {
void removeFromTopLayer();

#if ENABLE(FULLSCREEN_API)
bool containsFullScreenElement() const { return hasNodeFlag(NodeFlag::ContainsFullScreenElement); }
void setContainsFullScreenElement(bool);
void setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(bool);
bool hasFullscreenFlag() const { return hasNodeFlag(NodeFlag::IsFullscreen); }
bool hasIFrameFullscreenFlag() const { return hasNodeFlag(NodeFlag::IsIFrameFullscreen); }
void setFullscreenFlag(bool);
void setIFrameFullscreenFlag(bool);
WEBCORE_EXPORT void webkitRequestFullscreen();
virtual void requestFullscreen(FullscreenOptions&&, RefPtr<DeferredPromise>&&);
#endif

0 comments on commit c96dd4f

Please sign in to comment.