feat: add WebFrameMain.visibilityState (#28706)
* 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
This commit is contained in:
parent
93311c8686
commit
43d27cc4d1
5 changed files with 98 additions and 0 deletions
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
|
@ -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 } });
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue