Skip to content

Commit

Permalink
feat: add WebFrameMain.visibilityState (#28706)
Browse files Browse the repository at this point in the history
* feat: add WebFrameMain.visibilityState

* docs: mention other page visibility APIs

* test: delay visibilityState check after hiding

* test: add waitForTrue to avoid flaky visibilityState test

* refactor: waitForTrue -> waitUntil
  • Loading branch information
samuelmaddock committed Apr 22, 2021
1 parent 93311c8 commit 43d27cc
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 0 deletions.
6 changes: 6 additions & 0 deletions docs/api/web-frame-main.md
Expand Up @@ -182,3 +182,9 @@ This is not the same as the OS process ID; to read that use `frame.osProcessId`.
An `Integer` representing the unique frame id in the current renderer process.
Distinct `WebFrameMain` instances that refer to the same underlying frame will
have the same `routingId`.

#### `frame.visibilityState` _Readonly_

A `string` representing the [visibility state](https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilityState) of the frame.

See also how the [Page Visibility API](browser-window.md#page-visibility) is affected by other Electron APIs.
29 changes: 29 additions & 0 deletions shell/browser/api/electron_api_web_frame_main.cc
Expand Up @@ -30,6 +30,28 @@
#include "shell/common/node_includes.h"
#include "shell/common/v8_value_serializer.h"

namespace gin {

template <>
struct Converter<blink::mojom::PageVisibilityState> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
blink::mojom::PageVisibilityState val) {
std::string visibility;
switch (val) {
case blink::mojom::PageVisibilityState::kVisible:
visibility = "visible";
break;
case blink::mojom::PageVisibilityState::kHidden:
case blink::mojom::PageVisibilityState::kHiddenButPainting:
visibility = "hidden";
break;
}
return gin::ConvertToV8(isolate, visibility);
}
};

} // namespace gin

namespace electron {

namespace api {
Expand Down Expand Up @@ -228,6 +250,12 @@ GURL WebFrameMain::URL() const {
return render_frame_->GetLastCommittedURL();
}

blink::mojom::PageVisibilityState WebFrameMain::VisibilityState() const {
if (!CheckRenderFrame())
return blink::mojom::PageVisibilityState::kHidden;
return render_frame_->GetVisibilityState();
}

content::RenderFrameHost* WebFrameMain::Top() const {
if (!CheckRenderFrame())
return nullptr;
Expand Down Expand Up @@ -331,6 +359,7 @@ v8::Local<v8::ObjectTemplate> WebFrameMain::FillObjectTemplate(
.SetProperty("processId", &WebFrameMain::ProcessID)
.SetProperty("routingId", &WebFrameMain::RoutingID)
.SetProperty("url", &WebFrameMain::URL)
.SetProperty("visibilityState", &WebFrameMain::VisibilityState)
.SetProperty("top", &WebFrameMain::Top)
.SetProperty("parent", &WebFrameMain::Parent)
.SetProperty("frames", &WebFrameMain::Frames)
Expand Down
2 changes: 2 additions & 0 deletions shell/browser/api/electron_api_web_frame_main.h
Expand Up @@ -15,6 +15,7 @@
#include "gin/wrappable.h"
#include "shell/common/gin_helper/constructible.h"
#include "shell/common/gin_helper/pinnable.h"
#include "third_party/blink/public/mojom/page/page_visibility_state.mojom-forward.h"

class GURL;

Expand Down Expand Up @@ -95,6 +96,7 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain>,
int ProcessID() const;
int RoutingID() const;
GURL URL() const;
blink::mojom::PageVisibilityState VisibilityState() const;

content::RenderFrameHost* Top() const;
content::RenderFrameHost* Parent() const;
Expand Down
15 changes: 15 additions & 0 deletions spec-main/api-web-frame-main-spec.ts
Expand Up @@ -6,6 +6,7 @@ import { BrowserWindow, WebFrameMain, webFrameMain, ipcMain } from 'electron/mai
import { closeAllWindows } from './window-helpers';
import { emittedOnce, emittedNTimes } from './events-helpers';
import { AddressInfo } from 'net';
import { waitUntil } from './spec-helpers';

describe('webFrameMain module', () => {
const fixtures = path.resolve(__dirname, '..', 'spec-main', 'fixtures');
Expand Down Expand Up @@ -135,6 +136,20 @@ describe('webFrameMain module', () => {
});
});

describe('WebFrame.visibilityState', () => {
it('should match window state', async () => {
const w = new BrowserWindow({ show: true });
await w.loadURL('about:blank');
const webFrame = w.webContents.mainFrame;

expect(webFrame.visibilityState).to.equal('visible');
w.hide();
await expect(
waitUntil(() => webFrame.visibilityState === 'hidden')
).to.eventually.be.fulfilled();
});
});

describe('WebFrame.executeJavaScript', () => {
it('can inject code into any subframe', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });
Expand Down
46 changes: 46 additions & 0 deletions spec-main/spec-helpers.ts
Expand Up @@ -86,3 +86,49 @@ export async function startRemoteControlApp () {
defer(() => { appProcess.kill('SIGINT'); });
return new RemoteControlApp(appProcess, port);
}

export function waitUntil (
callback: () => boolean,
opts: { rate?: number, timeout?: number } = {}
) {
const { rate = 10, timeout = 10000 } = opts;
return new Promise<void>((resolve, reject) => {
let intervalId: NodeJS.Timeout | undefined; // eslint-disable-line prefer-const
let timeoutId: NodeJS.Timeout | undefined;

const cleanup = () => {
if (intervalId) clearInterval(intervalId);
if (timeoutId) clearTimeout(timeoutId);
};

const check = () => {
let result;

try {
result = callback();
} catch (e) {
cleanup();
reject(e);
return;
}

if (result === true) {
cleanup();
resolve();
return true;
}
};

if (check()) {
return;
}

intervalId = setInterval(check, rate);

timeoutId = setTimeout(() => {
timeoutId = undefined;
cleanup();
reject(new Error(`waitUntil timed out after ${timeout}ms`));
}, timeout);
});
}

0 comments on commit 43d27cc

Please sign in to comment.