mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
Adds navigation flags to reload a page unconditionally (#128671)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
a7b239f8d9
commit
efd5ce361e
16 changed files with 243 additions and 18 deletions
|
@ -25,5 +25,5 @@ export interface ApplicationStart
|
|||
| --- | --- |
|
||||
| [getUrlForApp(appId, options)](./kibana-plugin-core-public.applicationstart.geturlforapp.md) | Returns the absolute path (or URL) to a given app, including the global base path.<!-- -->By default, it returns the absolute path of the application (e.g <code>/basePath/app/my-app</code>). Use the <code>absolute</code> option to generate an absolute url instead (e.g <code>http://host:port/basePath/app/my-app</code>)<!-- -->Note that when generating absolute urls, the origin (protocol, host and port) are determined from the browser's current location. |
|
||||
| [navigateToApp(appId, options)](./kibana-plugin-core-public.applicationstart.navigatetoapp.md) | Navigate to a given app |
|
||||
| [navigateToUrl(url)](./kibana-plugin-core-public.applicationstart.navigatetourl.md) | Navigate to given URL in a SPA friendly way when possible (when the URL will redirect to a valid application within the current basePath).<!-- -->The method resolves pathnames the same way browsers do when resolving a <code><a href></code> value. The provided <code>url</code> can be: - an absolute URL - an absolute path - a path relative to the current URL (window.location.href)<!-- -->If all these criteria are true for the given URL: - (only for absolute URLs) The origin of the URL matches the origin of the browser's current location - The resolved pathname of the provided URL/path starts with the current basePath (eg. /mybasepath/s/my-space) - The pathname segment after the basePath matches any known application route (eg. /app/<id>/ or any application's <code>appRoute</code> configuration)<!-- -->Then a SPA navigation will be performed using <code>navigateToApp</code> using the corresponding application and path. Otherwise, fallback to a full page reload to navigate to the url using <code>window.location.assign</code> |
|
||||
| [navigateToUrl(url, options)](./kibana-plugin-core-public.applicationstart.navigatetourl.md) | Navigate to given URL in a SPA friendly way when possible (when the URL will redirect to a valid application within the current basePath).<!-- -->The method resolves pathnames the same way browsers do when resolving a <code><a href></code> value. The provided <code>url</code> can be: - an absolute URL - an absolute path - a path relative to the current URL (window.location.href)<!-- -->If all these criteria are true for the given URL: - (only for absolute URLs) The origin of the URL matches the origin of the browser's current location - The resolved pathname of the provided URL/path starts with the current basePath (eg. /mybasepath/s/my-space) - The pathname segment after the basePath matches any known application route (eg. /app/<id>/ or any application's <code>appRoute</code> configuration)<!-- -->Then a SPA navigation will be performed using <code>navigateToApp</code> using the corresponding application and path. Otherwise, fallback to a full page reload to navigate to the url using <code>window.location.assign</code> |
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ Then a SPA navigation will be performed using `navigateToApp` using the correspo
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
navigateToUrl(url: string): Promise<void>;
|
||||
navigateToUrl(url: string, options?: NavigateToUrlOptions): Promise<void>;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
@ -23,6 +23,7 @@ navigateToUrl(url: string): Promise<void>;
|
|||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| url | string | an absolute URL, an absolute path or a relative path, to navigate to. |
|
||||
| options | NavigateToUrlOptions | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
|
|
|
@ -85,6 +85,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [IHttpResponseInterceptorOverrides](./kibana-plugin-core-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. |
|
||||
| [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) |
|
||||
| [NavigateToAppOptions](./kibana-plugin-core-public.navigatetoappoptions.md) | Options for the [navigateToApp API](./kibana-plugin-core-public.applicationstart.navigatetoapp.md) |
|
||||
| [NavigateToUrlOptions](./kibana-plugin-core-public.navigatetourloptions.md) | Options for the [navigateToUrl API](./kibana-plugin-core-public.applicationstart.navigatetourl.md) |
|
||||
| [NotificationsSetup](./kibana-plugin-core-public.notificationssetup.md) | |
|
||||
| [NotificationsStart](./kibana-plugin-core-public.notificationsstart.md) | |
|
||||
| [OverlayBannersStart](./kibana-plugin-core-public.overlaybannersstart.md) | |
|
||||
|
|
|
@ -20,5 +20,6 @@ export interface NavigateToAppOptions
|
|||
| [openInNewTab?](./kibana-plugin-core-public.navigatetoappoptions.openinnewtab.md) | boolean | <i>(Optional)</i> if true, will open the app in new tab, will share session information via window.open if base |
|
||||
| [path?](./kibana-plugin-core-public.navigatetoappoptions.path.md) | string | <i>(Optional)</i> optional path inside application to deep link to. If undefined, will use [the app's default path](./kibana-plugin-core-public.app.defaultpath.md) as default. |
|
||||
| [replace?](./kibana-plugin-core-public.navigatetoappoptions.replace.md) | boolean | <i>(Optional)</i> if true, will not create a new history entry when navigating (using <code>replace</code> instead of <code>push</code>) |
|
||||
| [skipAppLeave?](./kibana-plugin-core-public.navigatetoappoptions.skipappleave.md) | boolean | <i>(Optional)</i> if true, will bypass the default onAppLeave behavior |
|
||||
| [state?](./kibana-plugin-core-public.navigatetoappoptions.state.md) | unknown | <i>(Optional)</i> optional state to forward to the application |
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [NavigateToAppOptions](./kibana-plugin-core-public.navigatetoappoptions.md) > [skipAppLeave](./kibana-plugin-core-public.navigatetoappoptions.skipappleave.md)
|
||||
|
||||
## NavigateToAppOptions.skipAppLeave property
|
||||
|
||||
if true, will bypass the default onAppLeave behavior
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
skipAppLeave?: boolean;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [NavigateToUrlOptions](./kibana-plugin-core-public.navigatetourloptions.md) > [forceRedirect](./kibana-plugin-core-public.navigatetourloptions.forceredirect.md)
|
||||
|
||||
## NavigateToUrlOptions.forceRedirect property
|
||||
|
||||
if true, will redirect directly to the url
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
forceRedirect?: boolean;
|
||||
```
|
|
@ -0,0 +1,21 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [NavigateToUrlOptions](./kibana-plugin-core-public.navigatetourloptions.md)
|
||||
|
||||
## NavigateToUrlOptions interface
|
||||
|
||||
Options for the [navigateToUrl API](./kibana-plugin-core-public.applicationstart.navigatetourl.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface NavigateToUrlOptions
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [forceRedirect?](./kibana-plugin-core-public.navigatetourloptions.forceredirect.md) | boolean | <i>(Optional)</i> if true, will redirect directly to the url |
|
||||
| [skipAppLeave?](./kibana-plugin-core-public.navigatetourloptions.skipappleave.md) | boolean | <i>(Optional)</i> if true, will bypass the default onAppLeave behavior |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [NavigateToUrlOptions](./kibana-plugin-core-public.navigatetourloptions.md) > [skipAppLeave](./kibana-plugin-core-public.navigatetourloptions.skipappleave.md)
|
||||
|
||||
## NavigateToUrlOptions.skipAppLeave property
|
||||
|
||||
if true, will bypass the default onAppLeave behavior
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
skipAppLeave?: boolean;
|
||||
```
|
|
@ -898,10 +898,9 @@ describe('#start()', () => {
|
|||
|
||||
it('should call private function shouldNavigate with overlays and the nextAppId', async () => {
|
||||
service.setup(setupDeps);
|
||||
|
||||
const shouldNavigateSpy = jest.spyOn(service as any, 'shouldNavigate');
|
||||
|
||||
const { navigateToApp } = await service.start(startDeps);
|
||||
|
||||
await navigateToApp('myTestApp');
|
||||
expect(shouldNavigateSpy).toHaveBeenCalledWith(startDeps.overlays, 'myTestApp');
|
||||
|
||||
|
@ -909,6 +908,14 @@ describe('#start()', () => {
|
|||
expect(shouldNavigateSpy).toHaveBeenCalledWith(startDeps.overlays, 'myOtherApp');
|
||||
});
|
||||
|
||||
it('should call private function shouldNavigate with overlays, nextAppId and skipAppLeave', async () => {
|
||||
service.setup(setupDeps);
|
||||
const shouldNavigateSpy = jest.spyOn(service as any, 'shouldNavigate');
|
||||
const { navigateToApp } = await service.start(startDeps);
|
||||
await navigateToApp('myTestApp', { skipAppLeave: true });
|
||||
expect(shouldNavigateSpy).not.toHaveBeenCalledWith(startDeps.overlays, 'myTestApp');
|
||||
});
|
||||
|
||||
describe('when `replace` option is true', () => {
|
||||
it('use `history.replace` instead of `history.push`', async () => {
|
||||
service.setup(setupDeps);
|
||||
|
@ -1117,6 +1124,63 @@ describe('#start()', () => {
|
|||
expect(MockHistory.push).toHaveBeenCalledWith('/app/foo/some-path', undefined);
|
||||
expect(setupDeps.redirectTo).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('navigateToUrl with options', () => {
|
||||
let addListenerSpy: jest.SpyInstance;
|
||||
let removeListenerSpy: jest.SpyInstance;
|
||||
beforeEach(() => {
|
||||
addListenerSpy = jest.spyOn(window, 'addEventListener');
|
||||
removeListenerSpy = jest.spyOn(window, 'removeEventListener');
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('calls `navigateToApp` with `skipAppLeave` option', async () => {
|
||||
parseAppUrlMock.mockReturnValue({ app: 'foo', path: '/some-path' });
|
||||
service.setup(setupDeps);
|
||||
const { navigateToUrl } = await service.start(startDeps);
|
||||
|
||||
await navigateToUrl('/an-app-path', { skipAppLeave: true });
|
||||
|
||||
expect(MockHistory.push).toHaveBeenCalledWith('/app/foo/some-path', undefined);
|
||||
expect(setupDeps.redirectTo).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls `redirectTo` when `forceRedirect` option is true', async () => {
|
||||
parseAppUrlMock.mockReturnValue({ app: 'foo', path: '/some-path' });
|
||||
service.setup(setupDeps);
|
||||
|
||||
const { navigateToUrl } = await service.start(startDeps);
|
||||
|
||||
await navigateToUrl('/an-app-path', { forceRedirect: true });
|
||||
|
||||
expect(addListenerSpy).toHaveBeenCalledTimes(1);
|
||||
expect(addListenerSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function));
|
||||
|
||||
expect(setupDeps.redirectTo).toHaveBeenCalledWith('/an-app-path');
|
||||
expect(MockHistory.push).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('removes the beforeunload listener and calls `redirectTo` when `forceRedirect` and `skipAppLeave` option are both true', async () => {
|
||||
parseAppUrlMock.mockReturnValue({ app: 'foo', path: '/some-path' });
|
||||
service.setup(setupDeps);
|
||||
|
||||
const { navigateToUrl } = await service.start(startDeps);
|
||||
|
||||
await navigateToUrl('/an-app-path', { skipAppLeave: true, forceRedirect: true });
|
||||
|
||||
expect(addListenerSpy).toHaveBeenCalledTimes(1);
|
||||
expect(addListenerSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function));
|
||||
const handler = addListenerSpy.mock.calls[0][1];
|
||||
|
||||
expect(MockHistory.push).toHaveBeenCalledTimes(0);
|
||||
expect(setupDeps.redirectTo).toHaveBeenCalledWith('/an-app-path');
|
||||
|
||||
expect(removeListenerSpy).toHaveBeenCalledTimes(1);
|
||||
expect(removeListenerSpy).toHaveBeenCalledWith('beforeunload', handler);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
InternalApplicationStart,
|
||||
Mounter,
|
||||
NavigateToAppOptions,
|
||||
NavigateToUrlOptions,
|
||||
} from './types';
|
||||
import { getLeaveAction, isConfirmAction } from './application_leave';
|
||||
import { getUserConfirmationHandler } from './navigation_confirm';
|
||||
|
@ -234,13 +235,19 @@ export class ApplicationService {
|
|||
|
||||
const navigateToApp: InternalApplicationStart['navigateToApp'] = async (
|
||||
appId,
|
||||
{ deepLinkId, path, state, replace = false, openInNewTab = false }: NavigateToAppOptions = {}
|
||||
{
|
||||
deepLinkId,
|
||||
path,
|
||||
state,
|
||||
replace = false,
|
||||
openInNewTab = false,
|
||||
skipAppLeave = false,
|
||||
}: NavigateToAppOptions = {}
|
||||
) => {
|
||||
const currentAppId = this.currentAppId$.value;
|
||||
const navigatingToSameApp = currentAppId === appId;
|
||||
const shouldNavigate = navigatingToSameApp
|
||||
? true
|
||||
: await this.shouldNavigate(overlays, appId);
|
||||
const shouldNavigate =
|
||||
navigatingToSameApp || skipAppLeave ? true : await this.shouldNavigate(overlays, appId);
|
||||
|
||||
const targetApp = applications$.value.get(appId);
|
||||
|
||||
|
@ -304,13 +311,20 @@ export class ApplicationService {
|
|||
return absolute ? relativeToAbsolute(relUrl) : relUrl;
|
||||
},
|
||||
navigateToApp,
|
||||
navigateToUrl: async (url) => {
|
||||
navigateToUrl: async (
|
||||
url: string,
|
||||
{ skipAppLeave = false, forceRedirect = false }: NavigateToUrlOptions = {}
|
||||
) => {
|
||||
const appInfo = parseAppUrl(url, http.basePath, this.apps);
|
||||
if (appInfo) {
|
||||
return navigateToApp(appInfo.app, { path: appInfo.path });
|
||||
} else {
|
||||
if ((forceRedirect || !appInfo) === true) {
|
||||
if (skipAppLeave) {
|
||||
window.removeEventListener('beforeunload', this.onBeforeUnload);
|
||||
}
|
||||
return this.redirectTo!(url);
|
||||
}
|
||||
if (appInfo) {
|
||||
return navigateToApp(appInfo.app, { path: appInfo.path, skipAppLeave });
|
||||
}
|
||||
},
|
||||
getComponent: () => {
|
||||
if (!this.history) {
|
||||
|
|
|
@ -28,6 +28,7 @@ export type {
|
|||
AppLeaveDefaultAction,
|
||||
AppLeaveConfirmAction,
|
||||
NavigateToAppOptions,
|
||||
NavigateToUrlOptions,
|
||||
PublicAppInfo,
|
||||
PublicAppDeepLinkInfo,
|
||||
// Internal types
|
||||
|
|
|
@ -170,7 +170,28 @@ describe('ApplicationService', () => {
|
|||
'/app/app1/deep-link',
|
||||
]);
|
||||
});
|
||||
////
|
||||
|
||||
it('handles `skipOnAppLeave` option', async () => {
|
||||
const { register } = service.setup(setupDeps);
|
||||
|
||||
register(Symbol(), {
|
||||
id: 'app1',
|
||||
title: 'App1',
|
||||
mount: async ({}: AppMountParameters) => {
|
||||
return () => undefined;
|
||||
},
|
||||
});
|
||||
|
||||
const { navigateToApp } = await service.start(startDeps);
|
||||
|
||||
await navigateToApp('app1', { path: '/foo' });
|
||||
await navigateToApp('app1', { path: '/bar', skipAppLeave: true });
|
||||
expect(history.entries.map((entry) => entry.pathname)).toEqual([
|
||||
'/',
|
||||
'/app/app1/foo',
|
||||
'/app/app1/bar',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -249,6 +270,38 @@ describe('ApplicationService', () => {
|
|||
expect(history.entries[2].pathname).toEqual('/app/app2');
|
||||
});
|
||||
|
||||
it('does not trigger the action if `skipAppLeave` is true', async () => {
|
||||
const { register } = service.setup(setupDeps);
|
||||
|
||||
register(Symbol(), {
|
||||
id: 'app1',
|
||||
title: 'App1',
|
||||
mount: ({ onAppLeave }: AppMountParameters) => {
|
||||
onAppLeave((actions) => actions.confirm('confirmation-message', 'confirmation-title'));
|
||||
return () => undefined;
|
||||
},
|
||||
});
|
||||
register(Symbol(), {
|
||||
id: 'app2',
|
||||
title: 'App2',
|
||||
mount: ({}: AppMountParameters) => {
|
||||
return () => undefined;
|
||||
},
|
||||
});
|
||||
|
||||
const { navigateToApp, getComponent } = await service.start(startDeps);
|
||||
|
||||
update = createRenderer(getComponent());
|
||||
|
||||
await act(async () => {
|
||||
await navigate('/app/app1');
|
||||
await navigateToApp('app2', { skipAppLeave: true });
|
||||
});
|
||||
expect(startDeps.overlays.openConfirm).toHaveBeenCalledTimes(0);
|
||||
expect(history.entries.length).toEqual(3);
|
||||
expect(history.entries[1].pathname).toEqual('/app/app1');
|
||||
});
|
||||
|
||||
it('blocks navigation to the new app if action is confirm and user declined', async () => {
|
||||
startDeps.overlays.openConfirm.mockResolvedValue(false);
|
||||
|
||||
|
|
|
@ -740,6 +740,26 @@ export interface NavigateToAppOptions {
|
|||
* if true, will open the app in new tab, will share session information via window.open if base
|
||||
*/
|
||||
openInNewTab?: boolean;
|
||||
|
||||
/**
|
||||
* if true, will bypass the default onAppLeave behavior
|
||||
*/
|
||||
skipAppLeave?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link ApplicationStart.navigateToUrl | navigateToUrl API}
|
||||
* @public
|
||||
*/
|
||||
export interface NavigateToUrlOptions {
|
||||
/**
|
||||
* if true, will bypass the default onAppLeave behavior
|
||||
*/
|
||||
skipAppLeave?: boolean;
|
||||
/**
|
||||
* if true will force a full page reload/refresh/assign, overriding the outcome of other url checks against current the location (effectively using `window.location.assign` instead of `push`)
|
||||
*/
|
||||
forceRedirect?: boolean;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -781,7 +801,7 @@ export interface ApplicationStart {
|
|||
* - The pathname segment after the basePath matches any known application route (eg. /app/<id>/ or any application's `appRoute` configuration)
|
||||
*
|
||||
* Then a SPA navigation will be performed using `navigateToApp` using the corresponding application and path.
|
||||
* Otherwise, fallback to a full page reload to navigate to the url using `window.location.assign`
|
||||
* Otherwise, fallback to a full page reload to navigate to the url using `window.location.assign`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
|
@ -802,8 +822,7 @@ export interface ApplicationStart {
|
|||
*
|
||||
* @param url - an absolute URL, an absolute path or a relative path, to navigate to.
|
||||
*/
|
||||
navigateToUrl(url: string): Promise<void>;
|
||||
|
||||
navigateToUrl(url: string, options?: NavigateToUrlOptions): Promise<void>;
|
||||
/**
|
||||
* Returns the absolute path (or URL) to a given app, including the global base path.
|
||||
*
|
||||
|
|
|
@ -91,6 +91,7 @@ export type {
|
|||
PublicAppInfo,
|
||||
PublicAppDeepLinkInfo,
|
||||
NavigateToAppOptions,
|
||||
NavigateToUrlOptions,
|
||||
} from './application';
|
||||
|
||||
export { SimpleSavedObject } from './saved_objects';
|
||||
|
|
|
@ -160,7 +160,7 @@ export interface ApplicationStart {
|
|||
deepLinkId?: string;
|
||||
}): string;
|
||||
navigateToApp(appId: string, options?: NavigateToAppOptions): Promise<void>;
|
||||
navigateToUrl(url: string): Promise<void>;
|
||||
navigateToUrl(url: string, options?: NavigateToUrlOptions): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
@ -778,9 +778,16 @@ export interface NavigateToAppOptions {
|
|||
openInNewTab?: boolean;
|
||||
path?: string;
|
||||
replace?: boolean;
|
||||
skipAppLeave?: boolean;
|
||||
state?: unknown;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface NavigateToUrlOptions {
|
||||
forceRedirect?: boolean;
|
||||
skipAppLeave?: boolean;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "NavType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
|
|
@ -18,11 +18,14 @@ import { SecurityPluginStart } from '../../../../../security/public';
|
|||
import { HttpLogic } from '../http';
|
||||
import { createHref, CreateHrefOptions } from '../react_router_helpers';
|
||||
|
||||
type RequiredFieldsOnly<T> = {
|
||||
[K in keyof T as T[K] extends Required<T>[K] ? K : never]: T[K];
|
||||
};
|
||||
interface KibanaLogicProps {
|
||||
config: { host?: string };
|
||||
// Kibana core
|
||||
history: History;
|
||||
navigateToUrl: ApplicationStart['navigateToUrl'];
|
||||
navigateToUrl: RequiredFieldsOnly<ApplicationStart['navigateToUrl']>;
|
||||
setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void;
|
||||
setChromeIsVisible(isVisible: boolean): void;
|
||||
setDocTitle(title: string): void;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue