Browse Source

test: BrowserWindow backgroundColor and transparency (#31017)

* test: BrowserWindow backgroundColor

* test: allow similar colors

* test: disable linux capturing

* refactor: split screen capture from reading pixel color
Samuel Maddock 3 years ago
parent
commit
7cb62bfc22

+ 65 - 0
spec-main/api-browser-window-spec.ts

@@ -12,6 +12,7 @@ import { app, BrowserWindow, BrowserView, dialog, ipcMain, OnBeforeSendHeadersLi
 import { emittedOnce, emittedUntil, emittedNTimes } from './events-helpers';
 import { ifit, ifdescribe, defer, delay } from './spec-helpers';
 import { closeWindow, closeAllWindows } from './window-helpers';
+import { areColorsSimilar, captureScreen, CHROMA_COLOR_HEX, getPixelColor } from './screen-helpers';
 
 const features = process._linkedBinding('electron_common_features');
 const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures');
@@ -4872,5 +4873,69 @@ describe('BrowserWindow module', () => {
       w.setBounds(newBounds);
       expect(w.getBounds()).to.deep.equal(newBounds);
     });
+
+    // Linux doesn't return any capture sources.
+    ifit(process.platform !== 'linux')('should not display a visible background', async () => {
+      const display = screen.getPrimaryDisplay();
+
+      const backgroundWindow = new BrowserWindow({
+        ...display.bounds,
+        frame: false,
+        backgroundColor: CHROMA_COLOR_HEX,
+        hasShadow: false
+      });
+
+      await backgroundWindow.loadURL('about:blank');
+
+      const foregroundWindow = new BrowserWindow({
+        ...display.bounds,
+        show: true,
+        transparent: true,
+        frame: false,
+        hasShadow: false
+      });
+
+      foregroundWindow.loadFile(path.join(__dirname, 'fixtures', 'pages', 'half-background-color.html'));
+      await emittedOnce(foregroundWindow, 'ready-to-show');
+
+      const screenCapture = await captureScreen();
+      const leftHalfColor = getPixelColor(screenCapture, {
+        x: display.size.width / 4,
+        y: display.size.height / 2
+      });
+      const rightHalfColor = getPixelColor(screenCapture, {
+        x: display.size.width - (display.size.width / 4),
+        y: display.size.height / 2
+      });
+
+      expect(areColorsSimilar(leftHalfColor, CHROMA_COLOR_HEX)).to.be.true();
+      expect(areColorsSimilar(rightHalfColor, '#ff0000')).to.be.true();
+    });
+  });
+
+  describe('"backgroundColor" option', () => {
+    afterEach(closeAllWindows);
+
+    // Linux doesn't return any capture sources.
+    ifit(process.platform !== 'linux')('should display the set color', async () => {
+      const display = screen.getPrimaryDisplay();
+
+      const w = new BrowserWindow({
+        ...display.bounds,
+        show: true,
+        backgroundColor: CHROMA_COLOR_HEX
+      });
+
+      w.loadURL('about:blank');
+      await emittedOnce(w, 'ready-to-show');
+
+      const screenCapture = await captureScreen();
+      const centerColor = getPixelColor(screenCapture, {
+        x: display.size.width / 2,
+        y: display.size.height / 2
+      });
+
+      expect(areColorsSimilar(centerColor, CHROMA_COLOR_HEX)).to.be.true();
+    });
   });
 });

+ 20 - 0
spec-main/fixtures/pages/half-background-color.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width">
+    <style>
+      #main {
+        position: fixed;
+        left: 50%;
+        right: 0;
+        top: 0;
+        bottom: 0;
+        background-color: #ff0000;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="main"></div>
+  </body>
+</html>

+ 87 - 0
spec-main/screen-helpers.ts

@@ -0,0 +1,87 @@
+import * as path from 'path';
+import * as fs from 'fs';
+import { screen, desktopCapturer, NativeImage } from 'electron';
+
+const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures');
+
+/** Chroma key green. */
+export const CHROMA_COLOR_HEX = '#00b140';
+
+/**
+ * Capture the screen at the given point.
+ *
+ * NOTE: Not yet supported on Linux in CI due to empty sources list.
+ */
+export const captureScreen = async (point: Electron.Point = { x: 0, y: 0 }): Promise<NativeImage> => {
+  const display = screen.getDisplayNearestPoint(point);
+  const sources = await desktopCapturer.getSources({ types: ['screen'], thumbnailSize: display.size });
+  // Toggle to save screen captures for debugging.
+  const DEBUG_CAPTURE = false;
+  if (DEBUG_CAPTURE) {
+    for (const source of sources) {
+      await fs.promises.writeFile(path.join(fixtures, `screenshot_${source.display_id}.png`), source.thumbnail.toPNG());
+    }
+  }
+  const screenCapture = sources.find(source => source.display_id === `${display.id}`);
+  // Fails when HDR is enabled on Windows.
+  // https://bugs.chromium.org/p/chromium/issues/detail?id=1247730
+  if (!screenCapture) {
+    const displayIds = sources.map(source => source.display_id);
+    throw new Error(`Unable to find screen capture for display '${display.id}'\n\tAvailable displays: ${displayIds.join(', ')}`);
+  }
+  return screenCapture.thumbnail;
+};
+
+const formatHexByte = (val: number): string => {
+  const str = val.toString(16);
+  return str.length === 2 ? str : `0${str}`;
+};
+
+/**
+ * Get the hex color at the given pixel coordinate in an image.
+ */
+export const getPixelColor = (image: Electron.NativeImage, point: Electron.Point): string => {
+  const pixel = image.crop({ ...point, width: 1, height: 1 });
+  // TODO(samuelmaddock): NativeImage.toBitmap() should return the raw pixel
+  // color, but it sometimes differs. Why is that?
+  const [b, g, r] = pixel.toBitmap();
+  return `#${formatHexByte(r)}${formatHexByte(g)}${formatHexByte(b)}`;
+};
+
+const hexToRgba = (hexColor: string) => {
+  const match = hexColor.match(/^#([0-9a-fA-F]{6,8})$/);
+  if (!match) return;
+
+  const colorStr = match[1];
+  return [
+    parseInt(colorStr.substring(0, 2), 16),
+    parseInt(colorStr.substring(2, 4), 16),
+    parseInt(colorStr.substring(4, 6), 16),
+    parseInt(colorStr.substring(6, 8), 16) || 0xFF
+  ];
+};
+
+/** Calculate euclidian distance between colors. */
+const colorDistance = (hexColorA: string, hexColorB: string) => {
+  const colorA = hexToRgba(hexColorA);
+  const colorB = hexToRgba(hexColorB);
+  if (!colorA || !colorB) return -1;
+  return Math.sqrt(
+    Math.pow(colorB[0] - colorA[0], 2) +
+    Math.pow(colorB[1] - colorA[1], 2) +
+    Math.pow(colorB[2] - colorA[2], 2)
+  );
+};
+
+/**
+ * Determine if colors are similar based on distance. This can be useful when
+ * comparing colors which may differ based on lossy compression.
+ */
+export const areColorsSimilar = (
+  hexColorA: string,
+  hexColorB: string,
+  distanceThreshold = 90
+): boolean => {
+  const distance = colorDistance(hexColorA, hexColorB);
+  return distance <= distanceThreshold;
+};