Browse Source

fix: check for draggable regions outside of main frame (#41388)

* fix: check for draggable regions outside of main frame

* fix: add nut-js to optional spec deps

Co-authored-by: samuelmaddock <[email protected]>

---------

Co-authored-by: samuelmaddock <[email protected]>
Keeley Hammond 1 year ago
parent
commit
09fbee9998

+ 4 - 4
shell/renderer/electron_render_frame_observer.cc

@@ -57,10 +57,10 @@ ElectronRenderFrameObserver::ElectronRenderFrameObserver(
   // Initialise resource for directory listing.
   net::NetModule::SetResourceProvider(NetResourceProvider);
 
-  // App regions are only supported in the main frame.
-  auto* main_frame = frame->GetMainRenderFrame();
-  if (main_frame && main_frame == frame)
-    render_frame_->GetWebView()->SetSupportsAppRegion(true);
+  // In Chrome, app regions are only supported in the main frame.
+  // However, we need to support draggable regions on other
+  // local frames/windows, so extend support beyond the main frame.
+  render_frame_->GetWebView()->SetSupportsAppRegion(true);
 }
 
 void ElectronRenderFrameObserver::DidClearWindowObject() {

+ 114 - 1
spec/api-browser-window-spec.ts

@@ -11,7 +11,7 @@ import { app, BrowserWindow, BrowserView, dialog, ipcMain, OnBeforeSendHeadersLi
 import { emittedUntil, emittedNTimes } from './lib/events-helpers';
 import { ifit, ifdescribe, defer, listen } from './lib/spec-helpers';
 import { closeWindow, closeAllWindows } from './lib/window-helpers';
-import { areColorsSimilar, captureScreen, HexColors, getPixelColor } from './lib/screen-helpers';
+import { areColorsSimilar, captureScreen, HexColors, getPixelColor, hasCapturableScreen } from './lib/screen-helpers';
 import { once } from 'node:events';
 import { setTimeout } from 'node:timers/promises';
 import { setTimeout as syncSetTimeout } from 'node:timers';
@@ -6599,4 +6599,117 @@ describe('BrowserWindow module', () => {
       expect(areColorsSimilar(centerColor, HexColors.BLUE)).to.be.true();
     });
   });
+
+  describe('draggable regions', () => {
+    afterEach(closeAllWindows);
+
+    ifit(hasCapturableScreen())('should allow the window to be dragged when enabled', async () => {
+      // WOA fails to load libnut so we're using require to defer loading only
+      // on supported platforms.
+      // "@nut-tree\libnut-win32\build\Release\libnut.node is not a valid Win32 application."
+      // @ts-ignore: nut-js is an optional dependency so it may not be installed
+      const { mouse, straightTo, centerOf, Region, Button } = require('@nut-tree/nut-js') as typeof import('@nut-tree/nut-js');
+
+      const display = screen.getPrimaryDisplay();
+
+      const w = new BrowserWindow({
+        x: 0,
+        y: 0,
+        width: display.bounds.width / 2,
+        height: display.bounds.height / 2,
+        frame: false,
+        titleBarStyle: 'hidden'
+      });
+
+      const overlayHTML = path.join(__dirname, 'fixtures', 'pages', 'overlay.html');
+      w.loadFile(overlayHTML);
+      await once(w, 'ready-to-show');
+
+      const winBounds = w.getBounds();
+      const titleBarHeight = 30;
+      const titleBarRegion = new Region(winBounds.x, winBounds.y, winBounds.width, titleBarHeight);
+      const screenRegion = new Region(display.bounds.x, display.bounds.y, display.bounds.width, display.bounds.height);
+
+      const startPos = w.getPosition();
+
+      await mouse.setPosition(await centerOf(titleBarRegion));
+      await mouse.pressButton(Button.LEFT);
+      await mouse.drag(straightTo(centerOf(screenRegion)));
+
+      // Wait for move to complete
+      await Promise.race([
+        once(w, 'move'),
+        setTimeout(100) // fallback for possible race condition
+      ]);
+
+      const endPos = w.getPosition();
+
+      expect(startPos).to.not.deep.equal(endPos);
+    });
+
+    ifit(hasCapturableScreen())('should allow the window to be dragged when no WCO and --webkit-app-region: drag enabled', async () => {
+      // @ts-ignore: nut-js is an optional dependency so it may not be installed
+      const { mouse, straightTo, centerOf, Region, Button } = require('@nut-tree/nut-js') as typeof import('@nut-tree/nut-js');
+
+      const display = screen.getPrimaryDisplay();
+      const w = new BrowserWindow({
+        x: 0,
+        y: 0,
+        width: display.bounds.width / 2,
+        height: display.bounds.height / 2,
+        frame: false
+      });
+
+      const basePageHTML = path.join(__dirname, 'fixtures', 'pages', 'base-page.html');
+      w.loadFile(basePageHTML);
+      await once(w, 'ready-to-show');
+
+      await w.webContents.executeJavaScript(`
+        const style = document.createElement('style');
+        style.innerHTML = \`
+        #titlebar {
+            
+          background-color: red;
+          height: 30px;
+          width: 100%;
+          -webkit-user-select: none;
+          -webkit-app-region: drag;
+          position: fixed;
+          top: 0;
+          left: 0;
+          z-index: 1000000000000;
+        }
+        \`;
+        
+        const titleBar = document.createElement('title-bar');
+        titleBar.id = 'titlebar';
+        titleBar.textContent = 'test-titlebar';
+        
+        document.body.append(style);
+        document.body.append(titleBar);
+      `);
+      // allow time for titlebar to finish loading
+      await setTimeout(2000);
+
+      const winBounds = w.getBounds();
+      const titleBarHeight = 30;
+      const titleBarRegion = new Region(winBounds.x, winBounds.y, winBounds.width, titleBarHeight);
+      const screenRegion = new Region(display.bounds.x, display.bounds.y, display.bounds.width, display.bounds.height);
+
+      const startPos = w.getPosition();
+      await mouse.setPosition(await centerOf(titleBarRegion));
+      await mouse.pressButton(Button.LEFT);
+      await mouse.drag(straightTo(centerOf(screenRegion)));
+
+      // Wait for move to complete
+      await Promise.race([
+        once(w, 'move'),
+        setTimeout(1000) // fallback for possible race condition
+      ]);
+
+      const endPos = w.getPosition();
+
+      expect(startPos).to.not.deep.equal(endPos);
+    });
+  });
 });

+ 13 - 0
spec/lib/screen-helpers.ts

@@ -91,3 +91,16 @@ export const areColorsSimilar = (
   const distance = colorDistance(hexColorA, hexColorB);
   return distance <= distanceThreshold;
 };
+
+/**
+ * Whether the current VM has a valid screen which can be used to capture.
+ *
+ * This is specific to Electron's CI test runners.
+ * - Linux: virtual screen display is 0x0
+ * - Win32 arm64 (WOA): virtual screen display is 0x0
+ * - Win32 ia32: skipped
+ */
+export const hasCapturableScreen = () => {
+  return process.platform === 'darwin' ||
+    (process.platform === 'win32' && process.arch === 'x64');
+};

+ 3 - 0
spec/package.json

@@ -38,6 +38,9 @@
     "ws": "^7.4.6",
     "yargs": "^16.0.3"
   },
+  "optionalDependencies": {
+    "@nut-tree/nut-js": "^3.1.2"
+  },
   "resolutions": {
     "nan": "file:../../third_party/nan",
     "dbus-native/optimist/minimist": "1.2.7",

File diff suppressed because it is too large
+ 714 - 5
spec/yarn.lock


Some files were not shown because too many files changed in this diff