Browse Source

fix: webview should maximize on requestFullscreen (#29988)

* fix: webview should maximize on requestFullscreen

* fix merge error

* chore: update patches

Co-authored-by: Cheng Zhao <[email protected]>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
trop[bot] 3 years ago
parent
commit
f1f9a76579

+ 1 - 0
patches/chromium/.patches

@@ -137,3 +137,4 @@ cherry-pick-b77b38a3380c.patch
 cherry-pick-910e9e40d376.patch
 cherry-pick-d9556a80a790.patch
 cherry-pick-ee6aee64e24c.patch
+webview_fullscreen.patch

+ 35 - 0
patches/chromium/webview_fullscreen.patch

@@ -0,0 +1,35 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Cheng Zhao <[email protected]>
+Date: Thu, 4 Oct 2018 14:57:02 -0700
+Subject: fix: also propagate fullscreen state for outer frame
+
+When entering fullscreen with Element.requestFullscreen in child frames,
+the parent frame should also enter fullscreen mode too. Chromium handles
+this for iframes, but not for webviews as they are essentially main
+frames instead of child frames.
+
+This patch makes webviews propagate the fullscreen state to embedder.
+
+Note that we also need to manually update embedder's
+`api::WebContents::IsFullscreenForTabOrPending` value.
+
+diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
+index 2e222a1bf9cb50efa990f875b102001998691f06..385080e04ed3fb6b82a1ed993889913546f0ba20 100644
+--- a/content/browser/renderer_host/render_frame_host_impl.cc
++++ b/content/browser/renderer_host/render_frame_host_impl.cc
+@@ -4728,6 +4728,15 @@ void RenderFrameHostImpl::EnterFullscreen(
+     notified_instances.insert(parent_site_instance);
+   }
+ 
++  // Entering fullscreen from webview should also notify its outer frame.
++  if (frame_tree_node()->render_manager()->IsMainFrameForInnerDelegate()) {
++    RenderFrameProxyHost* outer_proxy =
++        frame_tree_node()->render_manager()->GetProxyToOuterDelegate();
++    DCHECK(outer_proxy);
++    outer_proxy->GetAssociatedRemoteFrame()->WillEnterFullscreen(
++        options.Clone());
++  }
++
+   delegate_->EnterFullscreenMode(this, *options);
+   delegate_->FullscreenStateChanged(this, true /* is_fullscreen */,
+                                     std::move(options));

+ 35 - 3
shell/browser/api/electron_api_web_contents.cc

@@ -99,6 +99,7 @@
 #include "shell/browser/web_contents_zoom_controller.h"
 #include "shell/browser/web_dialog_helper.h"
 #include "shell/browser/web_view_guest_delegate.h"
+#include "shell/browser/web_view_manager.h"
 #include "shell/common/api/electron_api_native_image.h"
 #include "shell/common/color_util.h"
 #include "shell/common/electron_constants.h"
@@ -3534,13 +3535,13 @@ void WebContents::SetHtmlApiFullscreen(bool enter_fullscreen) {
   // Window is already in fullscreen mode, save the state.
   if (enter_fullscreen && owner_window_->IsFullscreen()) {
     native_fullscreen_ = true;
-    html_fullscreen_ = true;
+    UpdateHtmlApiFullscreen(true);
     return;
   }
 
   // Exit html fullscreen state but not window's fullscreen mode.
   if (!enter_fullscreen && native_fullscreen_) {
-    html_fullscreen_ = false;
+    UpdateHtmlApiFullscreen(false);
     return;
   }
 
@@ -3555,10 +3556,41 @@ void WebContents::SetHtmlApiFullscreen(bool enter_fullscreen) {
     owner_window_->SetFullScreen(enter_fullscreen);
   }
 
-  html_fullscreen_ = enter_fullscreen;
+  UpdateHtmlApiFullscreen(enter_fullscreen);
   native_fullscreen_ = false;
 }
 
+void WebContents::UpdateHtmlApiFullscreen(bool fullscreen) {
+  if (fullscreen == html_fullscreen_)
+    return;
+
+  html_fullscreen_ = fullscreen;
+
+  // Notify renderer of the html fullscreen change.
+  web_contents()
+      ->GetRenderViewHost()
+      ->GetWidget()
+      ->SynchronizeVisualProperties();
+
+  // The embedder WebContents is spearated from the frame tree of webview, so
+  // we must manually sync their fullscreen states.
+  if (embedder_)
+    embedder_->SetHtmlApiFullscreen(fullscreen);
+
+  // Make sure all child webviews quit html fullscreen.
+  if (!fullscreen && !IsGuest()) {
+    auto* manager = WebViewManager::GetWebViewManager(web_contents());
+    manager->ForEachGuest(
+        web_contents(), base::BindRepeating([](content::WebContents* guest) {
+          WebContents* api_web_contents = WebContents::From(guest);
+          // Use UpdateHtmlApiFullscreen instead of SetXXX becuase there is no
+          // need to interact with the owner window.
+          api_web_contents->UpdateHtmlApiFullscreen(false);
+          return false;
+        }));
+  }
+}
+
 // static
 v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
     v8::Isolate* isolate,

+ 2 - 0
shell/browser/api/electron_api_web_contents.h

@@ -693,6 +693,8 @@ class WebContents : public gin::Wrappable<WebContents>,
 
   // Set fullscreen mode triggered by html api.
   void SetHtmlApiFullscreen(bool enter_fullscreen);
+  // Update the html fullscreen flag in both browser and renderer.
+  void UpdateHtmlApiFullscreen(bool fullscreen);
 
   v8::Global<v8::Value> session_;
   v8::Global<v8::Value> devtools_web_contents_;

+ 0 - 1
shell/browser/web_view_manager.h

@@ -25,7 +25,6 @@ class WebViewManager : public content::BrowserPluginGuestManager {
 
   static WebViewManager* GetWebViewManager(content::WebContents* web_contents);
 
- protected:
   // content::BrowserPluginGuestManager:
   bool ForEachGuest(content::WebContents* embedder,
                     const GuestCallback& callback) override;

+ 12 - 0
spec-main/fixtures/webview/fullscreen/frame.html

@@ -0,0 +1,12 @@
+<body>
+  <div id="div">
+    WebView
+  </div>
+  <script type="text/javascript" charset="utf-8">
+    const {ipcRenderer} = require('electron')
+    ipcRenderer.send('webview-ready')
+    document.addEventListener('fullscreenchange', () => {
+      ipcRenderer.send('webview-fullscreenchange')
+    })
+  </script>
+</body>

+ 12 - 0
spec-main/fixtures/webview/fullscreen/main.html

@@ -0,0 +1,12 @@
+<body>
+  <webview id="webview" nodeintegration="on" webpreferences="contextIsolation=no" src="frame.html"/>
+  <script type="text/javascript" charset="utf-8">
+    document.addEventListener('fullscreenchange', () => {
+      require('electron').ipcRenderer.send('fullscreenchange')
+    })
+
+    function isIframeFullscreen() {
+      return document.getElementById('webview').shadowRoot.lastElementChild.matches(':fullscreen')
+    }
+  </script>
+</body>

+ 30 - 0
spec-main/webview-spec.ts

@@ -381,6 +381,36 @@ describe('<webview> tag', function () {
     });
   });
 
+  describe('requestFullscreen from webview', () => {
+    const loadWebViewWindow = async () => {
+      const w = new BrowserWindow({
+        webPreferences: {
+          webviewTag: true,
+          nodeIntegration: true,
+          contextIsolation: false
+        }
+      });
+      const attachPromise = emittedOnce(w.webContents, 'did-attach-webview');
+      const readyPromise = emittedOnce(ipcMain, 'webview-ready');
+      w.loadFile(path.join(__dirname, 'fixtures', 'webview', 'fullscreen', 'main.html'));
+      const [, webview] = await attachPromise;
+      await readyPromise;
+      return [w, webview];
+    };
+
+    afterEach(closeAllWindows);
+
+    it('should make parent frame element fullscreen too', 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();
+    });
+  });
+
   describe('nativeWindowOpen option', () => {
     let w: BrowserWindow;
     beforeEach(async () => {