fix: html fullscreen transitions stacking (#32905)

* fix: html fullscreen transitions stacking

* spec: move webview test to spec-main
This commit is contained in:
Shelley Vohr 2022-06-07 18:59:50 +02:00 коммит произвёл GitHub
Родитель f44ecb7f03
Коммит 16db5a112e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 184 добавлений и 61 удалений

Просмотреть файл

@ -3673,7 +3673,12 @@ void WebContents::EnumerateDirectory(
bool WebContents::IsFullscreenForTabOrPending(
const content::WebContents* source) {
return html_fullscreen_;
bool transition_fs = owner_window()
? owner_window()->fullscreen_transition_state() !=
NativeWindow::FullScreenTransitionState::NONE
: false;
return html_fullscreen_ || transition_fs;
}
bool WebContents::TakeFocus(content::WebContents* source, bool reverse) {
@ -3995,9 +4000,8 @@ void WebContents::SetHtmlApiFullscreen(bool enter_fullscreen) {
? !web_preferences->ShouldDisableHtmlFullscreenWindowResize()
: true;
if (html_fullscreenable) {
if (html_fullscreenable)
owner_window_->SetFullScreen(enter_fullscreen);
}
UpdateHtmlApiFullscreen(enter_fullscreen);
native_fullscreen_ = false;

Просмотреть файл

@ -718,6 +718,15 @@ std::string NativeWindow::GetAccessibleTitle() {
return base::UTF16ToUTF8(accessible_title_);
}
void NativeWindow::HandlePendingFullscreenTransitions() {
if (pending_transitions_.empty())
return;
bool next_transition = pending_transitions_.front();
pending_transitions_.pop();
SetFullScreen(next_transition);
}
// static
int32_t NativeWindow::next_id_ = 0;

Просмотреть файл

@ -7,6 +7,7 @@
#include <list>
#include <memory>
#include <queue>
#include <string>
#include <vector>
@ -317,6 +318,17 @@ class NativeWindow : public base::SupportsUserData,
observers_.RemoveObserver(obs);
}
enum class FullScreenTransitionState { ENTERING, EXITING, NONE };
// Handle fullscreen transitions.
void HandlePendingFullscreenTransitions();
void set_fullscreen_transition_state(FullScreenTransitionState state) {
fullscreen_transition_state_ = state;
}
FullScreenTransitionState fullscreen_transition_state() const {
return fullscreen_transition_state_;
}
views::Widget* widget() const { return widget_.get(); }
views::View* content_view() const { return content_view_; }
@ -375,6 +387,10 @@ class NativeWindow : public base::SupportsUserData,
// The "titleBarStyle" option.
TitleBarStyle title_bar_style_ = TitleBarStyle::kNormal;
std::queue<bool> pending_transitions_;
FullScreenTransitionState fullscreen_transition_state_ =
FullScreenTransitionState::NONE;
private:
std::unique_ptr<views::Widget> widget_;

Просмотреть файл

@ -8,7 +8,6 @@
#import <Cocoa/Cocoa.h>
#include <memory>
#include <queue>
#include <string>
#include <vector>
@ -172,11 +171,6 @@ class NativeWindowMac : public NativeWindow,
void SetCollectionBehavior(bool on, NSUInteger flag);
void SetWindowLevel(int level);
enum class FullScreenTransitionState { ENTERING, EXITING, NONE };
// Handle fullscreen transitions.
void SetFullScreenTransitionState(FullScreenTransitionState state);
void HandlePendingFullscreenTransitions();
bool HandleDeferredClose();
void SetHasDeferredWindowClose(bool defer_close) {
has_deferred_window_close_ = defer_close;
@ -247,13 +241,6 @@ class NativeWindowMac : public NativeWindow,
bool zoom_to_page_width_ = false;
absl::optional<gfx::Point> traffic_light_position_;
std::queue<bool> pending_transitions_;
FullScreenTransitionState fullscreen_transition_state() const {
return fullscreen_transition_state_;
}
FullScreenTransitionState fullscreen_transition_state_ =
FullScreenTransitionState::NONE;
// Trying to close an NSWindow during a fullscreen transition will cause the
// window to lock up. Use this to track if CloseWindow was called during a
// fullscreen transition, to defer the -[NSWindow close] call until the

Просмотреть файл

@ -582,11 +582,6 @@ bool NativeWindowMac::IsVisible() {
return [window_ isVisible] && !occluded && !IsMinimized();
}
void NativeWindowMac::SetFullScreenTransitionState(
FullScreenTransitionState state) {
fullscreen_transition_state_ = state;
}
bool NativeWindowMac::IsEnabled() {
return [window_ attachedSheet] == nil;
}
@ -670,15 +665,6 @@ bool NativeWindowMac::IsMinimized() {
return [window_ isMiniaturized];
}
void NativeWindowMac::HandlePendingFullscreenTransitions() {
if (pending_transitions_.empty())
return;
bool next_transition = pending_transitions_.front();
pending_transitions_.pop();
SetFullScreen(next_transition);
}
bool NativeWindowMac::HandleDeferredClose() {
if (has_deferred_window_close_) {
SetHasDeferredWindowClose(false);

Просмотреть файл

@ -237,7 +237,7 @@ using FullScreenTransitionState =
// Store resizable mask so it can be restored after exiting fullscreen.
is_resizable_ = shell_->HasStyleMask(NSWindowStyleMaskResizable);
shell_->SetFullScreenTransitionState(FullScreenTransitionState::ENTERING);
shell_->set_fullscreen_transition_state(FullScreenTransitionState::ENTERING);
shell_->NotifyWindowWillEnterFullScreen();
@ -246,7 +246,7 @@ using FullScreenTransitionState =
}
- (void)windowDidEnterFullScreen:(NSNotification*)notification {
shell_->SetFullScreenTransitionState(FullScreenTransitionState::NONE);
shell_->set_fullscreen_transition_state(FullScreenTransitionState::NONE);
shell_->NotifyWindowEnterFullScreen();
@ -257,13 +257,13 @@ using FullScreenTransitionState =
}
- (void)windowWillExitFullScreen:(NSNotification*)notification {
shell_->SetFullScreenTransitionState(FullScreenTransitionState::EXITING);
shell_->set_fullscreen_transition_state(FullScreenTransitionState::EXITING);
shell_->NotifyWindowWillLeaveFullScreen();
}
- (void)windowDidExitFullScreen:(NSNotification*)notification {
shell_->SetFullScreenTransitionState(FullScreenTransitionState::NONE);
shell_->set_fullscreen_transition_state(FullScreenTransitionState::NONE);
shell_->SetResizable(is_resizable_);
shell_->NotifyWindowLeaveFullScreen();

Просмотреть файл

@ -4731,19 +4731,80 @@ describe('BrowserWindow module', () => {
expect(w.isFullScreen()).to.be.false('is fullscreen');
});
it('handles several HTML fullscreen transitions', async () => {
const w = new BrowserWindow();
await w.loadFile(path.join(fixtures, 'pages', 'a.html'));
expect(w.isFullScreen()).to.be.false('is fullscreen');
const enterFullScreen = emittedOnce(w, 'enter-full-screen');
const leaveFullScreen = emittedOnce(w, 'leave-full-screen');
await w.webContents.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
await enterFullScreen;
await w.webContents.executeJavaScript('document.exitFullscreen()', true);
await leaveFullScreen;
expect(w.isFullScreen()).to.be.false('is fullscreen');
await delay();
await w.webContents.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
await enterFullScreen;
await w.webContents.executeJavaScript('document.exitFullscreen()', true);
await leaveFullScreen;
expect(w.isFullScreen()).to.be.false('is fullscreen');
});
it('handles several transitions in close proximity', async () => {
const w = new BrowserWindow();
expect(w.isFullScreen()).to.be.false('is fullscreen');
const enterFS = emittedNTimes(w, 'enter-full-screen', 2);
const leaveFS = emittedNTimes(w, 'leave-full-screen', 2);
w.setFullScreen(true);
w.setFullScreen(false);
w.setFullScreen(true);
w.setFullScreen(false);
const enterFullScreen = emittedNTimes(w, 'enter-full-screen', 2);
await enterFullScreen;
await Promise.all([enterFS, leaveFS]);
expect(w.isFullScreen()).to.be.true('not fullscreen');
expect(w.isFullScreen()).to.be.false('not fullscreen');
});
it('handles several chromium-initiated transitions in close proximity', async () => {
const w = new BrowserWindow();
await w.loadFile(path.join(fixtures, 'pages', 'a.html'));
expect(w.isFullScreen()).to.be.false('is fullscreen');
let enterCount = 0;
let exitCount = 0;
const done = new Promise<void>(resolve => {
const checkDone = () => {
if (enterCount === 2 && exitCount === 2) resolve();
};
w.webContents.on('enter-html-full-screen', () => {
enterCount++;
checkDone();
});
w.webContents.on('leave-html-full-screen', () => {
exitCount++;
checkDone();
});
});
await w.webContents.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
await w.webContents.executeJavaScript('document.exitFullscreen()');
await w.webContents.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
await w.webContents.executeJavaScript('document.exitFullscreen()');
await done;
});
it('does not crash when exiting simpleFullScreen (properties)', async () => {

Просмотреть файл

@ -10,7 +10,7 @@ import * as url from 'url';
import * as ChildProcess from 'child_process';
import { EventEmitter } from 'events';
import { promisify } from 'util';
import { ifit, ifdescribe, delay, defer } from './spec-helpers';
import { ifit, ifdescribe, defer, delay } from './spec-helpers';
import { AddressInfo } from 'net';
import { PipeTransport } from './pipe-transport';
@ -1590,7 +1590,7 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
server.close();
});
it('can fullscreen from out-of-process iframes (OOPIFs)', async () => {
ifit(process.platform !== 'darwin')('can fullscreen from out-of-process iframes (non-macOS)', async () => {
const fullscreenChange = emittedOnce(ipcMain, 'fullscreenChange');
const html =
'<iframe style="width: 0" frameborder=0 src="http://localhost:8989" allowfullscreen></iframe>';
@ -1614,8 +1614,37 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
expect(width).to.equal(0);
});
ifit(process.platform === 'darwin')('can fullscreen from out-of-process iframes (macOS)', async () => {
await emittedOnce(w, 'enter-full-screen');
const fullscreenChange = emittedOnce(ipcMain, 'fullscreenChange');
const html =
'<iframe style="width: 0" frameborder=0 src="http://localhost:8989" allowfullscreen></iframe>';
w.loadURL(`data:text/html,${html}`);
await fullscreenChange;
const fullscreenWidth = await w.webContents.executeJavaScript(
"document.querySelector('iframe').offsetWidth"
);
expect(fullscreenWidth > 0).to.be.true();
await w.webContents.executeJavaScript(
"document.querySelector('iframe').contentWindow.postMessage('exitFullscreen', '*')"
);
await emittedOnce(w.webContents, 'leave-html-full-screen');
const width = await w.webContents.executeJavaScript(
"document.querySelector('iframe').offsetWidth"
);
expect(width).to.equal(0);
w.setFullScreen(false);
await emittedOnce(w, 'leave-full-screen');
});
// TODO(jkleinsc) fix this flaky test on WOA
ifit(process.platform !== 'win32' || process.arch !== 'arm64')('can fullscreen from in-process iframes', async () => {
if (process.platform === 'darwin') await emittedOnce(w, 'enter-full-screen');
const fullscreenChange = emittedOnce(ipcMain, 'fullscreenChange');
w.loadFile(path.join(fixturesPath, 'pages', 'fullscreen-ipif.html'));
await fullscreenChange;

Просмотреть файл

@ -2,6 +2,7 @@
<div id="div">
WebView
</div>
<video></video>
<script type="text/javascript" charset="utf-8">
const {ipcRenderer} = require('electron')
ipcRenderer.send('webview-ready')

Просмотреть файл

@ -426,11 +426,16 @@ describe('<webview> tag', function () {
contextIsolation: false
}
});
const attachPromise = emittedOnce(w.webContents, 'did-attach-webview');
const loadPromise = emittedOnce(w.webContents, 'did-finish-load');
const readyPromise = emittedOnce(ipcMain, 'webview-ready');
w.loadFile(path.join(__dirname, 'fixtures', 'webview', 'fullscreen', 'main.html'));
const [, webview] = await attachPromise;
await readyPromise;
await Promise.all([readyPromise, loadPromise]);
return [w, webview];
};
@ -442,17 +447,38 @@ describe('<webview> tag', function () {
closeAllWindows();
});
it('should make parent frame element fullscreen too', async () => {
ifit(process.platform !== 'darwin')('should make parent frame element fullscreen too (non-macOS)', async () => {
const [w, webview] = await loadWebViewWindow();
expect(await w.webContents.executeJavaScript('isIframeFullscreen()')).to.be.false();
const parentFullscreen = emittedOnce(ipcMain, 'fullscreenchange');
await webview.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
await parentFullscreen;
expect(await w.webContents.executeJavaScript('isIframeFullscreen()')).to.be.true();
const close = emittedOnce(w, 'closed');
w.close();
await emittedOnce(w, 'closed');
await close;
});
ifit(process.platform === 'darwin')('should make parent frame element fullscreen too (macOS)', async () => {
const [w, webview] = await loadWebViewWindow();
expect(await w.webContents.executeJavaScript('isIframeFullscreen()')).to.be.false();
const parentFullscreen = emittedOnce(ipcMain, 'fullscreenchange');
const enterHTMLFS = emittedOnce(w.webContents, 'enter-html-full-screen');
const leaveHTMLFS = emittedOnce(w.webContents, 'leave-html-full-screen');
await webview.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
expect(await w.webContents.executeJavaScript('isIframeFullscreen()')).to.be.true();
await webview.executeJavaScript('document.exitFullscreen()');
await Promise.all([enterHTMLFS, leaveHTMLFS, parentFullscreen]);
const close = emittedOnce(w, 'closed');
w.close();
await close;
});
// FIXME(zcbenz): Fullscreen events do not work on Linux.
@ -468,8 +494,9 @@ describe('<webview> tag', function () {
await delay(0);
expect(w.isFullScreen()).to.be.false();
const close = emittedOnce(w, 'closed');
w.close();
await emittedOnce(w, 'closed');
await close;
});
// Sending ESC via sendInputEvent only works on Windows.
@ -485,8 +512,9 @@ describe('<webview> tag', function () {
await delay(0);
expect(w.isFullScreen()).to.be.false();
const close = emittedOnce(w, 'closed');
w.close();
await emittedOnce(w, 'closed');
await close;
});
it('pressing ESC should emit the leave-html-full-screen event', async () => {
@ -513,11 +541,27 @@ describe('<webview> tag', function () {
const leaveFSWindow = emittedOnce(w, 'leave-html-full-screen');
const leaveFSWebview = emittedOnce(webContents, 'leave-html-full-screen');
webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Escape' });
await leaveFSWindow;
await leaveFSWebview;
await leaveFSWindow;
const close = emittedOnce(w, 'closed');
w.close();
await emittedOnce(w, 'closed');
await close;
});
it('should support user gesture', async () => {
const [w, webview] = await loadWebViewWindow();
const waitForEnterHtmlFullScreen = emittedOnce(webview, 'enter-html-full-screen');
const jsScript = "document.querySelector('video').webkitRequestFullscreen()";
webview.executeJavaScript(jsScript, true);
await waitForEnterHtmlFullScreen;
const close = emittedOnce(w, 'closed');
w.close();
await close;
});
});

2
spec/fixtures/pages/fullscreen.html поставляемый
Просмотреть файл

@ -1 +1 @@
<video></video>
<video></video>

Просмотреть файл

@ -910,20 +910,6 @@ describe('<webview> tag', function () {
});
describe('executeJavaScript', () => {
it('should support user gesture', async () => {
await loadWebView(webview, {
src: `file://${fixtures}/pages/fullscreen.html`
});
// Event handler has to be added before js execution.
const waitForEnterHtmlFullScreen = waitForEvent(webview, 'enter-html-full-screen');
const jsScript = "document.querySelector('video').webkitRequestFullscreen()";
webview.executeJavaScript(jsScript, true);
return waitForEnterHtmlFullScreen;
});
it('can return the result of the executed script', async () => {
await loadWebView(webview, {
src: 'about:blank'