servoshell: ohos / android: Decouple initialization and window creation (#41532)

We can start initializing servo without needing to wait for the
creation of a window.
This allows us to perform initialization of servo itself,
concurrently to the app UI thread setting up the app UI.
Once the native window is ready, we can then let servo create
the first window and load the initial URL.
This is also interesting in the context of using servo as a
webview library, where loading the library / initialising the
webview and loading the first url are typically decoupled
steps.

Note: on the android port the Java code is not touched, which means that
we effectively still
perform the initialization at the same point in time on android. 
The change to call the base servo initialiation from the Java app, can
be done in a seperate PR, perhaps by someone more familiar with android
than me.

Follow-up PRs will add multi-window support, which means that some of
the multi-webview related code in this PR has open todos,
which will be addressed by follow-ups.



Testing: The ohos-port is tested in CI, the android port was manually
tested by me.

---------

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
Jonathan Schwender
2026-01-14 17:21:18 +01:00
committed by GitHub
parent e58bdc03f9
commit 66d8601c81
6 changed files with 262 additions and 233 deletions

View File

@@ -1 +1,14 @@
export const loadURL: (url: string) => void;
export interface InitOpts {
url: string;
resourceDir: string,
commandlineArgs: string,
}
export const loadURL: (url: string) => void;
export const goBack: () => void;
export const goForward: () => void;
export const registerURLcallback: (callback: (url: string) => void) => void;
export const registerTerminateCallback: (callback: () => void) => void;
export const registerPromptToastCallback: (callback: (msg: string) => void) => void;
export const focusWebview:(index: number) => void;
export const initServo:(options: InitOpts) => void;

View File

@@ -3,6 +3,7 @@ import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import window from '@ohos.window';
import servoshell, { InitOpts } from 'libservoshell.so';
const WRONG_COMMAND_ARRAY = ["tracing", "devtools", "force_ipc", "multiprocess", "webdriver"];
@@ -29,11 +30,11 @@ export default class EntryAbility extends UIAbility {
// See the aa tool documentation for more details:
// https://docs.openharmony.cn/pages/v5.0/en/application-dev/tools/aa-tool.md
if (key.startsWith("--")) {
let value = entry[1].toString()
params.push(key)
if (value) {
params.push(value)
}
let value = entry[1].toString()
params.push(key)
if (value) {
params.push(value)
}
} else if (WRONG_COMMAND_ARRAY.some(value => key.startsWith(value))) {
hilog.error(0xE0C3, "Servo EntryAbility", "-------------------------------------------------------------------------------------------------");
hilog.error(0xE0C3, "Servo EntryAbility", "\n\nYou probably meant to add -- infront of your argument, i.e., --psn=--tracing vs --psn=tracing. You used " + entry);
@@ -44,8 +45,15 @@ export default class EntryAbility extends UIAbility {
}
let servoshell_params = params.join("\u{001f}")
hilog.debug(0x0000, 'Servo EntryAbility', 'Servoshell parameters: %{public}s', servoshell_params);
this.init_params.setOrCreate('InitialURI', uri)
this.init_params.setOrCreate('CommandlineArgs', servoshell_params)
let resource_dir: string = this.context.resourceDir;
console.debug("resourceDir: ", resource_dir);
let init_options: InitOpts = {
url: uri,
resourceDir: resource_dir,
commandlineArgs: servoshell_params,
}
servoshell.initServo(init_options)
}
onDestroy() {

View File

@@ -1,30 +1,5 @@
import { common } from '@kit.AbilityKit';
import display from '@ohos.display';
import deviceInfo from '@ohos.deviceInfo';
import promptAction from '@ohos.promptAction';
interface ServoXComponentInterface {
loadURL(url: string): void;
goBack(): void;
goForward(): void;
registerURLcallback(callback: (url: string) => void): void;
registerTerminateCallback(callback: () => void): void;
registerPromptToastCallback(callback: (msg: string) => void): void
focusWebview(index: number):void;
initServo(options: InitOpts): void;
}
interface InitOpts {
url: string;
resourceDir: string,
commandlineArgs: string,
}
import servoshell from 'libservoshell.so';
function prompt_toast(msg: string) {
promptAction.showToast({
@@ -39,23 +14,29 @@ let storage = LocalStorage.getShared()
@Entry(storage)
@Component
struct Index {
xComponentContext: ServoXComponentInterface | undefined = undefined;
xComponentAttrs: XComponentAttrs = {
id: 'ServoDemo',
type: XComponentType.SURFACE,
libraryname: 'servoshell',
}
private context = getContext(this) as common.UIAbilityContext;
@LocalStorageProp('InitialURI') InitialURI: string = "unused"
@LocalStorageProp('CommandlineArgs') CommandlineArgs: string = ""
@State urlToLoad: string = this.InitialURI
@State tablist: Array<number> = [];
@State urlToLoad: string = ""
@State tablist: Array<number> = [1];
@State currentIndex: number = 0;
aboutToAppear(): void {
console.info("aboutToAppear - registering callbacks!")
servoshell.registerURLcallback((new_url: string) => {
console.info('New URL from native: ', new_url)
this.urlToLoad = new_url
})
servoshell.registerPromptToastCallback(prompt_toast)
}
// Called when the user swipes from the right or left edge to the middle
// Default behavior is bringing the app to the background.
onBackPress(): boolean | void {
this.xComponentContext?.goBack()
onBackPress(): boolean {
servoshell.goBack();
return true;
}
@@ -72,15 +53,8 @@ struct Index {
.fontSize(22)
.width('12%')
.onClick((event) => {
if (this.tablist.length==0) {
this.tablist.push(2);
} else {
this.tablist.push(this.tablist[this.tablist.length-1]+1);
}
// yes this is correct as we always have one tab extra
// The tab extra is seperate for the initialization and will always exist.
// It is not in the tablist.
this.currentIndex = this.tablist.length;
this.tablist.push(this.tablist[this.tablist.length-1]+1);
this.currentIndex = this.tablist.length - 1;
})
Button('⇦')
.backgroundColor(Color.White)
@@ -98,7 +72,7 @@ struct Index {
.fontSize(12)
.width('12%')
.onClick(() => {
this.xComponentContext?.goForward()
servoshell.goForward()
})
TextInput({ placeholder: 'URL', text: $$this.urlToLoad })
.type(InputType.URL)
@@ -107,34 +81,12 @@ struct Index {
this.urlToLoad = value
})
.onSubmit((EnterKeyType) => {
this.xComponentContext?.loadURL(this.urlToLoad)
servoshell.loadURL(this.urlToLoad)
console.info('Load URL: ', this.urlToLoad)
})
}
Tabs({ barPosition: BarPosition.Start, index: this.currentIndex}) {
TabContent() {
XComponent(this.xComponentAttrs)
.focusable(true)
.onLoad((xComponentContext) => {
this.xComponentContext = xComponentContext as ServoXComponentInterface;
let resource_dir: string = this.context.resourceDir;
let cache_dir: string = this.context.cacheDir;
console.debug("resourceDir: ", resource_dir);
console.debug("cacheDir: ", cache_dir);
let init_options: InitOpts = {
url: this.urlToLoad,
resourceDir: resource_dir,
commandlineArgs: this.CommandlineArgs
}
this.xComponentContext.initServo(init_options)
this.xComponentContext.registerURLcallback((new_url) => {
console.info('New URL from native: ', new_url)
this.urlToLoad = new_url
})
this.xComponentContext.registerPromptToastCallback(prompt_toast)
})
}.tabBar('1')
ForEach(this.tablist, (item: number) => {
TabContent() {
XComponent(this.xComponentAttrs)
@@ -142,7 +94,8 @@ struct Index {
}.tabBar(String(item))
})
}.onChange((index: number) => {
this.xComponentContext?.focusWebview(index);
console.info("Focusing Tab number %d", index)
servoshell.focusWebview(index);
})
}
.width('100%')