Skip to content

Commit

Permalink
[iOS] Element Fullscreen does not respect viewport-fit
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=264012
rdar://117304719

Reviewed by Wenson Hsieh and Tim Horton.

Tests: fast/viewport/ios/full-screen-safe-area-insets.html
       fullscreen/full-screen-document-background-color.html

When configuring the WKWebView during the enter fullscreen operation, various settings of the view
must be returned to their default state for the "automatic" avoid-safe-areas behavior to kick in.
For some calls made by clients, there is no way to reset those behaviors to default, and the
existing implementation merely overrode those settings with other non-default values. The end
result was that all fullscreen content was behaving as if `viewport-fit=cover` was specified, which
allowed some content to slip into the safe areas.

Additionally, when embedded content is taken fullscreen, the viewport settings of that embedded
iframe are not respected, and the embedded content uses the viewport settings of whatever page
embedded it. Also, the fullscreen element's background is not used in the overflow areas when
iframe content is in fullscreen.

* Source/WebCore/dom/Document.cpp:
(WebCore::Document::updateViewportArguments):
* Source/WebCore/dom/FullscreenManager.cpp:
(WebCore::FullscreenManager::dispatchFullscreenChangeOrErrorEvent):
(WebCore::FullscreenManager::deepestFullscreenDocument const):
* Source/WebCore/dom/FullscreenManager.h:
* Source/WebCore/page/LocalFrameView.cpp:
(WebCore::LocalFrameView::documentBackgroundColor const):
* Source/WebCore/page/Page.cpp:
(WebCore::viewportDocumentForFrame):
(WebCore::Page::viewportArguments const):
* Source/WebKit/UIProcess/API/ios/WKWebViewIOS.h:
* Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm:
(-[WKWebView _resetScrollViewInsetAdjustmentBehavior]):
(-[WKWebView _haveSetUnobscuredSafeAreaInsets]):
(-[WKWebView _resetUnobscuredSafeAreaInsets]):
(-[WKWebView _hasOverriddenLayoutParameters]):
(-[WKWebView _viewLayoutSizeOverride]):
(-[WKWebView _minimumUnobscuredSizeOverride]):
(-[WKWebView _maximumUnobscuredSizeOverride]):
(-[WKWebView _resetObscuredInsets]):
(-[WKWebView _clearOverrideLayoutParameters]):
* Source/WebKit/UIProcess/ios/WKContentView.mm:
(-[WKContentView setFrame:]):
* Source/WebKit/UIProcess/ios/WKScrollView.h:
* Source/WebKit/UIProcess/ios/WKScrollView.mm:
(-[WKScrollView _contentInsetWasExternallyOverridden]):
(-[WKScrollView _resetContentInset]):
(-[WKScrollView _resetContentInsetAdjustmentBehavior]):
* Source/WebKit/UIProcess/ios/fullscreen/WKFullScreenViewController.mm:
(-[WKFullScreenViewController viewDidLayoutSubviews]):
(-[WKFullScreenViewController viewWillTransitionToSize:withTransitionCoordinator:]):
* Source/WebKit/UIProcess/ios/fullscreen/WKFullScreenWindowControllerIOS.mm:
(WebKit::WKWebViewState::applyTo):
(WebKit::WKWebViewState::store):
(-[WKFullScreenWindowController enterFullScreen:]):
(-[WKFullScreenWindowController beganEnterFullScreenWithInitialFrame:finalFrame:]):

Canonical link: https://commits.webkit.org/270199@main
  • Loading branch information
jernoble committed Nov 3, 2023
1 parent 97076b3 commit 56d49b0
Show file tree
Hide file tree
Showing 21 changed files with 523 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@

Set main frame viewport-fit=contain and test iframes
RUN(metaViewport.content = "width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no, viewport-fit=contain")
RUN(setSafeAreaInsets(100, 50))
EXPECTED (visibleRect.left == '-100') OK
EXPECTED (visibleRect.top == '-50') OK

Test that fullscreen within an iframe with viewport-fit-cover has viewport-fit-cover behavior:
RUN(frame.src = "resources/viewport-fit-cover.html")
EVENT(load)
RUN(enterFullscreen())
EXPECTED (visibleRect.left == '0') OK
EXPECTED (visibleRect.top == '0') OK
RUN(exitFullscreen())

Test that fullscreen within an iframe with viewport-fit-contain has viewport-fit-contain behavior:
RUN(frame.src = "resources/viewport-fit-contain.html")
EVENT(load)
RUN(enterFullscreen())
EXPECTED (visibleRect.left == '-100') OK
EXPECTED (visibleRect.top == '-50') OK
RUN(exitFullscreen())

Set main frame viewport-fit=cover and re-test iframes
RUN(metaViewport.content = "width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no, viewport-fit=cover")
EXPECTED (visibleRect.left == '0') OK
EXPECTED (visibleRect.top == '0') OK

RUN(frame.src = "resources/viewport-fit-cover.html")
EVENT(load)
Test that fullscreen within an iframe with viewport-fit-cover has viewport-fit-cover behavior:
RUN(enterFullscreen())
EXPECTED (visibleRect.left == '0') OK
EXPECTED (visibleRect.top == '0') OK
RUN(exitFullscreen())

Test that fullscreen within an iframe with viewport-fit-contain has viewport-fit-contain behavior:
RUN(frame.src = "resources/viewport-fit-contain.html")
EVENT(load)
RUN(enterFullscreen())
EXPECTED (visibleRect.left == '-100') OK
EXPECTED (visibleRect.top == '-50') OK
RUN(exitFullscreen())

RUN(setSafeAreaInsets(0, 0))
END OF TEST

130 changes: 130 additions & 0 deletions LayoutTests/fast/viewport/ios/full-screen-safe-area-insets.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>full-screen-safe-area-insets</title>
<script src="../../../fullscreen/full-screen-test.js"></script>
<script src="resources/viewport-test-utils.js"></script>
<script>
function setSafeAreaInsets(left, top) {
const script = `(function() {
uiController.setSafeAreaInsets(${top}, 0, 0, ${left});
uiController.doAfterVisibleContentRectUpdate(() => {
uiController.uiScriptComplete();
})
})();`;
return new Promise(resolve => {
testRunner.runUIScript(script, () => {
resolve();
});
});
}

function getVisibleRect() {
const script = `(function() {
return JSON.stringify(uiController.contentVisibleRect);
})();`
return new Promise(resolve => {
testRunner.runUIScript(script, value => {
var result = JSON.parse(value);
resolve(result);
})
});
}

async function enterFullscreen() {
return new Promise(resolve => {
internals.withUserGesture(() => {
frame.contentDocument.documentElement.requestFullscreen().then(resolve, resolve);
});
});
}

async function exitFullscreen() {
return new Promise(resolve => {
internals.withUserGesture(() => {
frame.contentDocument.exitFullscreen().then(resolve, resolve);
});
});
}

async function testExpectedVisibleRect(left, top) {
window.scrollTo(0, 0);
// await sleepFor(100);
window.visibleRect = await getVisibleRect();
testExpected('visibleRect.left', left)
testExpected('visibleRect.top', top)
}

async function runTest() {
consoleDiv = document.getElementById('console');
consoleWrite('Set main frame viewport-fit=contain and test iframes')
window.metaViewport = document.querySelector('meta[name="viewport"]');
run('metaViewport.content = "width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no, viewport-fit=contain"')
await run('setSafeAreaInsets(100, 50)');
await testExpectedVisibleRect(-100, -50);

consoleWrite('')

consoleWrite('Test that fullscreen within an iframe with viewport-fit-cover has viewport-fit-cover behavior:')
run('frame.src = "resources/viewport-fit-cover.html"');
await waitFor(frame, 'load');

await run('enterFullscreen()');

await testExpectedVisibleRect(0, 0);

await run('exitFullscreen()');
consoleWrite('')

consoleWrite('Test that fullscreen within an iframe with viewport-fit-contain has viewport-fit-contain behavior:')
run('frame.src = "resources/viewport-fit-contain.html"');
await waitFor(frame, 'load');
await run('enterFullscreen()');
await testExpectedVisibleRect(-100, -50);
await run('exitFullscreen()');
consoleWrite('')

consoleWrite('Set main frame viewport-fit=cover and re-test iframes')
run('metaViewport.content = "width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"')
await testExpectedVisibleRect(0, 0);
consoleWrite('')

run('frame.src = "resources/viewport-fit-cover.html"');
await waitFor(frame, 'load');

consoleWrite('Test that fullscreen within an iframe with viewport-fit-cover has viewport-fit-cover behavior:')
await run('enterFullscreen()');
await testExpectedVisibleRect(0, 0);
await run('exitFullscreen()');
consoleWrite('')

consoleWrite('Test that fullscreen within an iframe with viewport-fit-contain has viewport-fit-contain behavior:')
run('frame.src = "resources/viewport-fit-contain.html"');
await waitFor(frame, 'load');
await run('enterFullscreen()');
await testExpectedVisibleRect(-100, -50);
await run('exitFullscreen()');
consoleWrite('')

await run('setSafeAreaInsets(0, 0)');
}

window.addEventListener('load', event => {
if (!window.internals) {
consoleWrite('This test requires internals; please run in WKTR/DRT.');
return;
}
runTest().finally(endTest);
})
</script>
<style>
#console { height: 200px; overflow: scroll; }
</style>
</head>
<body>
<iframe id=frame width=100 height=50></iframe>
<div id=console></div>
</body>
</html>
15 changes: 15 additions & 0 deletions LayoutTests/fast/viewport/ios/resources/viewport-fit-contain.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1,viewport-fit=contain">
</head>
<script>
window.addEventListener('load', event => {
document.body.onclick = function() {
document.body.webkitRequestFullScreen();
};
});
</script>
<body>
</body>
</html>
15 changes: 15 additions & 0 deletions LayoutTests/fast/viewport/ios/resources/viewport-fit-cover.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1,viewport-fit=cover">
</head>
<script>
window.addEventListener('load', event => {
document.body.onclick = function() {
document.body.webkitRequestFullScreen();
};
});
</script>
<body>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

EXPECTED (internals.documentBackgroundColor() == 'rgb(255, 255, 255)') OK
RUN(enterFullscreen())
EXPECTED (internals.documentBackgroundColor() == 'rgb(0, 128, 0)') OK
RUN(exitFullscreen())
EXPECTED (internals.documentBackgroundColor() == 'rgb(255, 255, 255)') OK
END OF TEST

40 changes: 40 additions & 0 deletions LayoutTests/fullscreen/full-screen-document-background-color.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>full-screen-document-background-color</title>
<style>html { background-color: white; }</style>
<script src="full-screen-test.js"></script>
<script>
async function enterFullscreen() {
return new Promise(resolve => {
internals.withUserGesture(() => {
frame.contentDocument.body.requestFullscreen().then(resolve, resolve);
});
});
}

async function exitFullscreen() {
return new Promise(resolve => {
internals.withUserGesture(() => {
frame.contentDocument.exitFullscreen().then(resolve, resolve);
});
});
}

async function runTest() {
testExpected('internals.documentBackgroundColor()', 'rgb(255, 255, 255)');
await run('enterFullscreen()');
testExpected('internals.documentBackgroundColor()', 'rgb(0, 128, 0)');
await run('exitFullscreen()');
testExpected('internals.documentBackgroundColor()', 'rgb(255, 255, 255)');
}
window.addEventListener('load', event => {
runTest().finally(endTest);
})
</script>
</head>
<body>
<iframe id=frame width=100 height=50 src="resources/green.html"></iframe>
</body>
2 changes: 1 addition & 1 deletion LayoutTests/fullscreen/full-screen-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function run(testFuncString)
{
consoleWrite("RUN(" + testFuncString + ")");
try {
eval(testFuncString);
return eval(testFuncString);
} catch (ex) {
consoleWrite(ex);
}
Expand Down
21 changes: 16 additions & 5 deletions Source/WebCore/dom/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4230,13 +4230,24 @@ ViewportArguments Document::viewportArguments() const
void Document::updateViewportArguments()
{
CheckedPtr page = this->page();
if (page && frame()->isMainFrame()) {
if (!page)
return;

bool isViewportDocument = [&] {
#if ENABLE(FULLSCREEN_API)
if (auto* outermostFullscreenDocument = page->outermostFullscreenDocument())
return outermostFullscreenDocument == this;
#endif
return frame()->isMainFrame();
}();
if (!isViewportDocument)
return;

#if ASSERT_ENABLED
m_didDispatchViewportPropertiesChanged = true;
m_didDispatchViewportPropertiesChanged = true;
#endif
page->chrome().dispatchViewportPropertiesDidChange(viewportArguments());
page->chrome().didReceiveDocType(protectedFrame().releaseNonNull());
}
page->chrome().dispatchViewportPropertiesDidChange(viewportArguments());
page->chrome().didReceiveDocType(protectedFrame().releaseNonNull());
}

void Document::metaElementThemeColorChanged(HTMLMetaElement& metaElement)
Expand Down
3 changes: 3 additions & 0 deletions Source/WebCore/dom/FullscreenManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,9 @@ void FullscreenManager::dispatchFullscreenChangeOrErrorEvent(Deque<GCReachableRe
queue.append(*element);
}

// Gaining or losing fullscreen state may change viewport arguments
node->protectedDocument()->updateViewportArguments();

#if ENABLE(VIDEO)
if (shouldNotifyMediaElement && is<HTMLMediaElement>(node.get()))
downcast<HTMLMediaElement>(node.get()).enteredOrExitedFullscreen();
Expand Down
32 changes: 29 additions & 3 deletions Source/WebCore/page/LocalFrameView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4641,19 +4641,45 @@ Color LocalFrameView::documentBackgroundColor() const
// Background images are unfortunately impractical to include.

// Return invalid Color objects whenever there is insufficient information.
if (!m_frame->document())
auto* backgroundDocument = [&] {
#if ENABLE(FULLSCREEN_API)
if (auto* page = m_frame->page()) {
if (auto* fullscreenDocument = page->outermostFullscreenDocument())
return fullscreenDocument;
}
#endif
return m_frame->document();
}();

if (!backgroundDocument)
return Color();

auto* htmlElement = m_frame->document()->documentElement();
auto* bodyElement = m_frame->document()->bodyOrFrameset();
auto* htmlElement = backgroundDocument->documentElement();
auto* bodyElement = backgroundDocument->bodyOrFrameset();
#if ENABLE(FULLSCREEN_API)
auto* fullscreenElement = backgroundDocument->fullscreenManager().fullscreenElement();
#else
Element* fullscreenElement = nullptr;
#endif

// Start with invalid colors.
Color htmlBackgroundColor;
Color bodyBackgroundColor;
Color fullscreenBackgroundColor;
if (htmlElement && htmlElement->renderer())
htmlBackgroundColor = htmlElement->renderer()->style().visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
if (bodyElement && bodyElement->renderer())
bodyBackgroundColor = bodyElement->renderer()->style().visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
if (fullscreenElement && fullscreenElement->renderer())
fullscreenBackgroundColor = fullscreenElement->renderer()->style().visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);

// Replace or blend the fullscreen background color with the body background color, if present.
if (fullscreenBackgroundColor.isValid()) {
if (!bodyBackgroundColor.isValid())
bodyBackgroundColor = fullscreenBackgroundColor;
else
bodyBackgroundColor = blendSourceOver(bodyBackgroundColor, fullscreenBackgroundColor);
}

if (!bodyBackgroundColor.isValid()) {
if (!htmlBackgroundColor.isValid())
Expand Down
Loading

0 comments on commit 56d49b0

Please sign in to comment.