Skip to content

Commit 68625de

Browse files
committed
Add webview manager
1 parent 27205eb commit 68625de

23 files changed

+895
-307
lines changed

libs/component/runtime/resources/stubs/saucer.stub.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,39 @@
1515
final class CSaucerWebViewEventsStruct extends CData
1616
{
1717
/**
18-
* @var \Closure(CData):void
18+
* @var \Closure(CData, CData):void
1919
*/
2020
public \Closure $onDomReady;
2121

2222
/**
23-
* @var \Closure(CData, string):void
23+
* @var \Closure(CData, string, CData):void
2424
*/
2525
public \Closure $onNavigated;
2626

2727
/**
28-
* @var \Closure(CData, CData):void
28+
* @var \Closure(CData, CData, CData):void
2929
*/
3030
public \Closure $onNavigating;
3131

3232
/**
33-
* @var \Closure(CData, CData):void
33+
* @var \Closure(CData, CData, CData):void
3434
*/
3535
public \Closure $onFaviconChanged;
3636

3737
/**
38-
* @var \Closure(CData, string):void
38+
* @var \Closure(CData, string, CData):void
3939
*/
4040
public \Closure $onTitleChanged;
4141

4242
/**
43-
* @var \Closure(CData, array{State::SAUCER_STATE_*}):void
43+
* @var \Closure(CData, State::SAUCER_STATE_*, CData):void
4444
*/
4545
public \Closure $onLoad;
46+
47+
/**
48+
* @var \Closure(CData, string, int<0, max>, CData):void
49+
*/
50+
public \Closure $onMessage;
4651
}
4752

4853
}

libs/component/runtime/src/Application.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,7 @@ private function onWindowClose(): void
358358
*/
359359
private function onApplicationStarted(): void
360360
{
361-
// Resolve main window lazy proxy (facade)
362-
$_ = $this->window->isClosed;
361+
$this->windows->boot();
363362
}
364363

365364
/**

libs/component/runtime/src/Internal/Poller/SaucerPoller.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ final class SaucerPoller implements PollerInterface
4040
private readonly CData $ptr;
4141

4242
public function __construct(
43-
private readonly ApplicationId $id,
43+
ApplicationId $id,
4444
private readonly SaucerInterface $saucer,
4545
) {
4646
$this->ids = $this->createIdValueGenerator();
47-
$this->ptr = $this->id->ptr;
47+
$this->ptr = $this->saucer->saucer_loop_new($id->ptr);
4848
}
4949

5050
/**
@@ -89,7 +89,7 @@ public function next(): void
8989

9090
private function executeInternalTask(): void
9191
{
92-
$this->saucer->saucer_application_run_once($this->ptr);
92+
$this->saucer->saucer_loop_iteration($this->ptr);
9393
}
9494

9595
private function executePeriodicTask(): void
@@ -156,4 +156,9 @@ public function cancel(int|string $taskId): void
156156
{
157157
unset($this->periodicMicroTasks[$taskId], $this->microTasks[$taskId]);
158158
}
159+
160+
public function __destruct()
161+
{
162+
$this->saucer->saucer_loop_free($this->ptr);
163+
}
159164
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Boson\Internal;
6+
7+
use Boson\Contracts\Id\IntIdInterface;
8+
9+
/**
10+
* @template-implements IntIdInterface<int<0, max>>
11+
*/
12+
abstract readonly class PositiveIntId implements IntIdInterface
13+
{
14+
final protected function __construct(
15+
/**
16+
* @var int<0, max>
17+
*/
18+
protected int $id,
19+
) {}
20+
21+
/**
22+
* @param int<0, max> $id
23+
*/
24+
public static function new(int $id): static
25+
{
26+
return new static($id);
27+
}
28+
29+
public function toInteger(): int
30+
{
31+
return $this->id;
32+
}
33+
34+
public function equals(mixed $other): bool
35+
{
36+
return $other === $this
37+
|| ($other instanceof self
38+
&& $this->id === $other->id);
39+
}
40+
41+
public function __debugInfo(): array
42+
{
43+
return [
44+
'id' => $this->id,
45+
];
46+
}
47+
48+
/**
49+
* @return non-empty-string
50+
*/
51+
public function __toString(): string
52+
{
53+
return \sprintf('%s(%d)', static::class, $this->id);
54+
}
55+
}

libs/component/runtime/src/WebView/Api/LifecycleEvents/LifecycleEventsListener.php

Lines changed: 78 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,30 @@ final class LifecycleEventsListener extends LoadedWebViewExtension
3434
*/
3535
private const string WEBVIEW_HANDLER_STRUCT = <<<'CDATA'
3636
struct {
37-
void (*onDomReady)(const saucer_handle *);
38-
void (*onNavigated)(const saucer_handle *, const char *);
39-
SAUCER_POLICY (*onNavigating)(const saucer_handle *, const saucer_navigation *);
40-
void (*onFaviconChanged)(const saucer_handle *, const saucer_icon *);
41-
void (*onTitleChanged)(const saucer_handle *, const char *);
42-
void (*onLoad)(const saucer_handle *, const SAUCER_STATE *);
37+
// TODO Add permissions event
38+
// TODO Add fullscreen event
39+
40+
// saucer_webview_event_dom_ready
41+
void (*onDomReady)(const saucer_webview *, void *);
42+
43+
// saucer_webview_event_navigated
44+
// TODO Add saucer_url support
45+
void (*onNavigated)(const saucer_webview *, saucer_url *, void *);
46+
47+
// saucer_webview_event_navigate
48+
SAUCER_POLICY (*onNavigating)(const saucer_webview *, const saucer_navigation *, void *);
49+
50+
// saucer_webview_event_favicon
51+
void (*onFaviconChanged)(const saucer_webview *, saucer_icon *, void *);
52+
53+
// saucer_webview_event_title
54+
void (*onTitleChanged)(const saucer_webview *, const char *, size_t, void *);
55+
56+
// saucer_webview_event_load
57+
void (*onLoad)(const saucer_webview *, SAUCER_STATE, void *);
58+
59+
// saucer_webview_event_message
60+
void (*onMessage)(const saucer_webview *, const char *, size_t, void *);
4361
}
4462
CDATA;
4563

@@ -80,6 +98,7 @@ private function createEventHandlers(): CData
8098
$struct->onFaviconChanged = $this->onSafeFaviconChanged(...);
8199
$struct->onTitleChanged = $this->onSafeTitleChanged(...);
82100
$struct->onLoad = $this->onSafeLoad(...);
101+
$struct->onMessage = $this->onSafeMessageReceived(...);
83102

84103
return $struct;
85104
}
@@ -89,16 +108,15 @@ private function listenEvents(): void
89108
/** @phpstan-var CSaucerWebViewEventsStruct $ctx */
90109
$ctx = $this->handlers;
91110

92-
$ptr = $this->webview->window->id->ptr;
111+
$ptr = $this->webview->id->ptr;
93112

94-
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_DOM_READY, $ctx->onDomReady);
95-
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_NAVIGATED, $ctx->onNavigated);
96-
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_NAVIGATE, $ctx->onNavigating);
97-
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_FAVICON, $ctx->onFaviconChanged);
98-
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_TITLE, $ctx->onTitleChanged);
99-
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_LOAD, $ctx->onLoad);
100-
101-
$this->app->saucer->saucer_webview_on_message($ptr, $this->onSafeMessageReceived(...));
113+
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_DOM_READY, $ctx->onDomReady, false, null);
114+
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_NAVIGATED, $ctx->onNavigated, false, null);
115+
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_NAVIGATE, $ctx->onNavigating, false, null);
116+
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_FAVICON, $ctx->onFaviconChanged, false, null);
117+
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_TITLE, $ctx->onTitleChanged, false, null);
118+
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_LOAD, $ctx->onLoad, false, null);
119+
$this->app->saucer->saucer_webview_on($ptr, Event::SAUCER_WEBVIEW_EVENT_MESSAGE, $ctx->onLoad, false, null);
102120
}
103121

104122
private function onMessageReceived(string $message): bool
@@ -111,7 +129,7 @@ private function onMessageReceived(string $message): bool
111129
return $event->isPropagationStopped;
112130
}
113131

114-
private function onSafeMessageReceived(string $message): bool
132+
private function onSafeMessageReceived(CData $_, string $message, int $size): bool
115133
{
116134
try {
117135
return $this->onMessageReceived($message);
@@ -140,19 +158,19 @@ private function onSafeDomReady(CData $_): void
140158
}
141159
}
142160

143-
private function onNavigated(CData $_, string $url): void
161+
private function onNavigated(CData $_, CData $url): void
144162
{
145163
try {
146164
$this->dispatch(new WebViewNavigated(
147165
subject: $this->webview,
148-
url: Request::castUrl($url),
166+
url: Request::castUrl($this->urlToString($url)),
149167
));
150168
} catch (\Throwable $e) {
151169
$this->webview->window->app->poller->throw($e);
152170
}
153171
}
154172

155-
private function onSafeNavigated(CData $_, string $url): void
173+
private function onSafeNavigated(CData $_, CData $url): void
156174
{
157175
try {
158176
$this->onNavigated($_, $url);
@@ -161,25 +179,37 @@ private function onSafeNavigated(CData $_, string $url): void
161179
}
162180
}
163181

182+
private function urlToString(CData $url): string
183+
{
184+
$value = $this->app->saucer->new('char');
185+
$size = $this->app->saucer->new('size_t');
186+
187+
$this->app->saucer->saucer_url_string($url, \FFI::addr($value), \FFI::addr($size));
188+
189+
if ($size->cdata === 0) {
190+
return '';
191+
}
192+
193+
return \FFI::string($value, $size->cdata);
194+
}
195+
164196
private function onNavigating(CData $_, CData $navigation): int
165197
{
166198
$this->changeState(WebViewState::Navigating);
167199

168-
$url = \FFI::string($this->app->saucer->saucer_navigation_url($navigation));
200+
$saucerUrl = $this->app->saucer->saucer_navigation_url($navigation);
201+
$bosonUrl = Request::castUrl($this->urlToString($saucerUrl));
202+
$this->app->saucer->saucer_url_free($saucerUrl);
169203

170-
try {
171-
return $this->intent(new WebViewNavigating(
172-
subject: $this->webview,
173-
url: Request::castUrl($url),
174-
isNewWindow: $this->app->saucer->saucer_navigation_new_window($navigation),
175-
isRedirection: $this->app->saucer->saucer_navigation_redirection($navigation),
176-
isUserInitiated: $this->app->saucer->saucer_navigation_user_initiated($navigation),
177-
))
178-
? Policy::SAUCER_POLICY_ALLOW
179-
: Policy::SAUCER_POLICY_BLOCK;
180-
} finally {
181-
$this->app->saucer->saucer_navigation_free($navigation);
182-
}
204+
return $this->intent(new WebViewNavigating(
205+
subject: $this->webview,
206+
url: $bosonUrl,
207+
isNewWindow: $this->app->saucer->saucer_navigation_new_window($navigation),
208+
isRedirection: $this->app->saucer->saucer_navigation_redirection($navigation),
209+
isUserInitiated: $this->app->saucer->saucer_navigation_user_initiated($navigation),
210+
))
211+
? Policy::SAUCER_POLICY_ALLOW
212+
: Policy::SAUCER_POLICY_BLOCK;
183213
}
184214

185215
private function onSafeNavigating(CData $_, CData $navigation): int
@@ -199,13 +229,9 @@ private function onFaviconChanged(CData $ptr, CData $icon): void
199229
return;
200230
}
201231

202-
try {
203-
$this->app->saucer->saucer_window_set_icon($ptr, $icon);
232+
$this->app->saucer->saucer_window_set_icon($this->webview->window->id->ptr, $icon);
204233

205-
$this->dispatch(new WebViewFaviconChanged($this->webview));
206-
} finally {
207-
$this->app->saucer->saucer_icon_free($icon);
208-
}
234+
$this->dispatch(new WebViewFaviconChanged($this->webview));
209235
}
210236

211237
private function onSafeFaviconChanged(CData $ptr, CData $icon): void
@@ -217,28 +243,31 @@ private function onSafeFaviconChanged(CData $ptr, CData $icon): void
217243
}
218244
}
219245

220-
private function onTitleChanged(CData $ptr, string $title): void
246+
private function onTitleChanged(CData $ptr, string $title, int $length): void
221247
{
222248
if (!$this->intent(new WebViewTitleChanging($this->webview, $title))) {
223249
return;
224250
}
225251

226-
$this->app->saucer->saucer_window_set_title($ptr, $title);
252+
$this->app->saucer->saucer_window_set_title($this->window->id->ptr, $title);
227253
$this->dispatch(new WebViewTitleChanged($this->webview, $title));
228254
}
229255

230-
private function onSafeTitleChanged(CData $ptr, string $title): void
256+
private function onSafeTitleChanged(CData $ptr, string $title, int $length): void
231257
{
232258
try {
233-
$this->onTitleChanged($ptr, $title);
259+
$this->onTitleChanged($ptr, $title, $length);
234260
} catch (\Throwable $e) {
235261
$this->webview->window->app->poller->throw($e);
236262
}
237263
}
238264

239-
private function onLoad(CData $_, CData $state): void
265+
/**
266+
* @param State::SAUCER_STATE_* $state
267+
*/
268+
private function onLoad(CData $_, int $state): void
240269
{
241-
if ($state[0] === State::SAUCER_STATE_STARTED) {
270+
if ($state === State::SAUCER_STATE_STARTED) {
242271
$this->changeState(WebViewState::Loading);
243272

244273
return;
@@ -247,7 +276,10 @@ private function onLoad(CData $_, CData $state): void
247276
$this->changeState(WebViewState::Ready);
248277
}
249278

250-
private function onSafeLoad(CData $_, CData $state): void
279+
/**
280+
* @param State::SAUCER_STATE_* $state
281+
*/
282+
private function onSafeLoad(CData $_, int $state): void
251283
{
252284
$this->onLoad($_, $state);
253285
}

libs/component/runtime/src/WebView/Api/Scripts/LoadedScript.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Boson\Component\Saucer\SaucerInterface;
88
use Boson\Contracts\Id\IdentifiableInterface;
9+
use Boson\WebView\WebView;
910
use JetBrains\PhpStorm\Language;
1011

1112
final readonly class LoadedScript implements
@@ -14,6 +15,7 @@
1415
{
1516
public function __construct(
1617
private SaucerInterface $api,
18+
public WebView $webView,
1719
public LoadedScriptId $id,
1820
#[Language('JavaScript')]
1921
public string $code,
@@ -28,6 +30,9 @@ public function __toString(): string
2830

2931
public function __destruct()
3032
{
31-
$this->api->saucer_script_free($this->id->ptr);
33+
$this->api->saucer_webview_uninject(
34+
$this->webView->id->ptr,
35+
$this->id->toInteger(),
36+
);
3237
}
3338
}

0 commit comments

Comments
 (0)