Skip to content

Commit b28fb63

Browse files
authored
Support multiple windows. (#15)
* Support multiple windows. * Bump version. Fix for Linux. * Refactor. * Fix typo. * Fix bind/unbind on Windows. * Fix for Windows. * Add a note in `run`'s docs.
1 parent b62d0dd commit b28fb63

File tree

19 files changed

+155
-71
lines changed

19 files changed

+155
-71
lines changed

.github/workflows/CI.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@ jobs:
2222
fail-fast: false
2323
matrix:
2424
julia-arch: [x64]
25-
julia-version: ["1.8", "~1.9.0-0", "nightly"]
25+
julia-version: ["1.8", "1.9", "nightly"]
2626
os: [ubuntu-latest, windows-latest, macOS-latest]
27-
exclude:
28-
- os: ubuntu-latest
29-
julia-version: "1.8"
3027
include:
3128
- os: ubuntu-latest
3229
prefix: xvfb-run

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "Webviews"
22
uuid = "b7e775b7-052a-4229-9250-fe08ae90f9c6"
33
authors = ["Sunoru <[email protected]>"]
4-
version = "1.1.1"
4+
version = "1.2.0"
55

66
[deps]
77
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
@@ -18,7 +18,7 @@ WebIO = "0f1e0344-ec1d-5b48-a673-e5cf874b6c29"
1818
[compat]
1919
Downloads = "1.6"
2020
FunctionWrappers = "1.1"
21-
JSON3 = "1.12"
21+
JSON3 = "1.13"
2222
Reexport = "1.2"
2323
WebIO = "0.8"
2424
julia = "1.8"

examples/bind.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ bind(webview, "resize") do _
3333
end
3434
bind(println, webview, "log")
3535
bind(webview, "terminate") do _
36-
terminate(webview)
36+
terminate()
3737
end
3838

3939
run(webview)

examples/multiple.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ navigate!(webview1, "https://julialang.org/")
66
webview2 = Webview()
77
navigate!(webview2, "https://google.com/")
88

9+
# This will show both windows.
910
run(webview1)
10-
run(webview2)

src/API.jl

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ export terminate,
1818
using Base64: Base64
1919
using JSON3: JSON3
2020

21+
using ..Webviews: Webviews
2122
using ..Consts
2223
using ..Consts: WebviewStatus, WEBVIEW_PENDING, WEBVIEW_RUNNING, WEBVIEW_DESTORYED
24+
using ..Utils
2325
abstract type AbstractWebview end
2426
abstract type AbstractPlatformImpl end
2527

@@ -35,13 +37,11 @@ macro forward(expr)
3537
end
3638

3739
"""
38-
terminate(w::Webview)
40+
terminate()
3941
4042
Stops the main loop. It is safe to call this function from another other background thread.
41-
42-
**Note:** This function is not working on macOS.
4343
"""
44-
@forward terminate(w)
44+
terminate() = Webviews.PlatformImpl.terminate()
4545

4646
"""
4747
close(w::Webview)
@@ -59,12 +59,14 @@ Destroys the webview and closes the window along with freeing all internal resou
5959
"""
6060
function destroy(w::AbstractWebview)
6161
w.status WEBVIEW_DESTORYED && return
62+
window = window_handle(w.platform)
6263
for key in keys(w.callback_handler.callbacks)
6364
unbind(w, key)
6465
end
65-
is_shown(w.platform) && terminate(w)
66+
is_shown(w.platform) && close(w)
6667
destroy(w.platform)
6768
w.status = WEBVIEW_DESTORYED
69+
Webviews.on_window_destroy(window)
6870
nothing
6971
end
7072
destroy(::AbstractPlatformImpl) = nothing
@@ -74,6 +76,8 @@ destroy(::AbstractPlatformImpl) = nothing
7476
7577
Runs the webview event loop. Runs the main event loop until it's terminated.
7678
After this function exits, the webview is automatically destroyed.
79+
80+
**Note**: This function will show all webview windows that were created.
7781
"""
7882
function Base.run(w::AbstractWebview)
7983
w.status WEBVIEW_DESTORYED && error("Webview is already destroyed")

src/Utils.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ module Utils
33
using FunctionWrappers: FunctionWrapper
44
using JSON3: JSON3
55

6-
using ..API
7-
86
const MessageCallback = FunctionWrapper{Nothing,Tuple{Int,Vector{Any}}}
97

108
# JuliaLang/julia #269
@@ -37,7 +35,7 @@ function on_message(ch::CallbackHandler, s::Ptr{Cchar})
3735
nothing
3836
end
3937

40-
function API.bind_raw(f::Function, ch::CallbackHandler, name::AbstractString)
38+
function bind_raw(f::Function, ch::CallbackHandler, name::AbstractString)
4139
haskey(ch.callbacks, name) && return
4240
ch.callbacks[name] = MessageCallback() do seq, args
4341
f(seq, args)
@@ -46,7 +44,7 @@ function API.bind_raw(f::Function, ch::CallbackHandler, name::AbstractString)
4644
nothing
4745
end
4846

49-
function API.unbind(ch::CallbackHandler, name::AbstractString)
47+
function unbind(ch::CallbackHandler, name::AbstractString)
5048
delete!(ch.callbacks, name)
5149
nothing
5250
end

src/Webviews.jl

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ using JSON3: JSON3
1313
export Webview
1414

1515
include("./Consts.jl")
16-
include("./API.jl")
1716
include("./Utils.jl")
17+
include("./API.jl")
1818

1919
# Platform-specific implementations
2020
@static if Sys.isapple()
@@ -32,7 +32,15 @@ using .Consts: WebviewStatus, WEBVIEW_PENDING, WEBVIEW_RUNNING, WEBVIEW_DESTORYE
3232
@reexport using .API
3333

3434
"""
35-
Webview(size=(1024, 768); title="", debug=false, size_fixed=false, unsafe_window_handle=C_NULL)
35+
Webview(
36+
size=(1024, 768);
37+
title="",
38+
debug=false,
39+
size_fixed=false,
40+
unsafe_window_handle=C_NULL,
41+
enable_webio=true,
42+
auto_terminate=true
43+
)
3644
Webview(width, height; kwargs...)
3745
3846
Create a new webview instance with `size` and `title`.
@@ -42,11 +50,14 @@ Create a new webview instance with `size` and `title`.
4250
If it's non-null - then child WebView is embedded into the given parent window.
4351
Otherwise a new window is created. Depending on the platform,
4452
a `GtkWindow`, `NSWindow` or `HWND` pointer can be passed here.
53+
- If `enable_webio` is true, then WebIO will be enabled.
54+
- The process will be terminated when all webviews with `auto_terminate=true` are destroyed.
4555
"""
4656
mutable struct Webview <: API.AbstractWebview
4757
const platform::PlatformImpl.Webview
4858
const callback_handler::Utils.CallbackHandler
4959
const webio_enabled::Bool
60+
const auto_terminate::Bool
5061
status::Consts.WebviewStatus
5162
function Webview(
5263
size::Tuple{Integer,Integer}=(1024, 768);
@@ -55,16 +66,23 @@ mutable struct Webview <: API.AbstractWebview
5566
size_fixed::Bool=false,
5667
unsafe_window_handle::Ptr{Cvoid}=C_NULL,
5768
enable_webio::Bool=true,
69+
auto_terminate::Bool=true
5870
)
5971
ch = Utils.CallbackHandler()
6072
platform = PlatformImpl.Webview(ch, debug, unsafe_window_handle)
61-
window_handle(platform) C_NULL && error("Failed to create webview window")
62-
w = new(platform, ch, enable_webio, WEBVIEW_PENDING)
73+
window = window_handle(platform)
74+
window C_NULL && error("Failed to create webview window")
75+
w = new(platform, ch, enable_webio, auto_terminate, WEBVIEW_PENDING)
6376
API.resize!(w, size; hint=size_fixed ? WEBVIEW_HINT_FIXED : WEBVIEW_HINT_NONE)
6477
title!(w, title)
6578
if enable_webio
6679
webio_init!(w)
6780
end
81+
if auto_terminate
82+
lock(ActiveWindowsLock) do
83+
ActiveWindows[window] = w
84+
end
85+
end
6886
finalizer(destroy, w)
6987
end
7088
end
@@ -85,6 +103,21 @@ function Base.show(io::IO, w::Webview)
85103
)
86104
end
87105

106+
# Window => Webview
107+
const ActiveWindows = Dict{Ptr{Cvoid}, Webview}()
108+
const ActiveWindowsLock = ReentrantLock()
109+
110+
function on_window_destroy(w::Ptr{Cvoid})
111+
lock(ActiveWindowsLock) do
112+
haskey(ActiveWindows, w) || return
113+
webview = pop!(ActiveWindows, w)
114+
destroy(webview)
115+
if isempty(ActiveWindows)
116+
terminate()
117+
end
118+
end
119+
end
120+
88121
function __init__()
89122
if PlatformImpl.check_dependency()
90123
PlatformImpl.setup_platform()

src/platforms/apple/Impl.jl

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
module AppleImpl
2-
# TODO
32

43
include("../common.jl")
54
include("../common_bind.jl")
@@ -32,6 +31,9 @@ function setup_platform()
3231
C_NULL,
3332
true::Bool
3433
)
34+
delegate = create_app_delegate()
35+
app = get_shared_application()
36+
@msg_send Cvoid app a"setDelegate:"sel delegate
3537
prepare_timeout()
3638
nothing
3739
end
@@ -59,27 +61,13 @@ function Webview(
5961
debug,
6062
callback_handler
6163
)
62-
this_ptr = pointer_from_objref(w)
6364
app = get_shared_application()
64-
delegate = create_app_delegate(w)
65-
@ccall objc_setAssociatedObject(
66-
delegate::ID,
67-
ASSOCIATED_KEY::Cstring,
68-
this_ptr::ID,
69-
0::UInt # OBJC_ASSOCIATION_ASSIGN
70-
)::ID
71-
@msg_send Cvoid app a"setDelegate:"sel delegate
72-
if unsafe_window_handle C_NULL
73-
@msg_send Cvoid app a"run"sel
74-
else
75-
on_application_did_finish_launching(w, delegate, app)
76-
end
65+
on_application_did_finish_launching(w, app)
7766
w
7867
end
7968

8069
API.window_handle(w::Webview) = w.window
81-
# TODO: support multiple windows.
82-
API.terminate(::Webview) =
70+
terminate() =
8371
let app = get_shared_application()
8472
# Stop the main event loop instead of terminating the process.
8573
@msg_send Cvoid app a"stop:"sel C_NULL

src/platforms/apple/utils.jl

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function get_associated_webview(self::ID)
88
unsafe_pointer_to_objref(Ptr{Webview}(w))
99
end
1010

11-
function create_app_delegate(w::Webview)
11+
function create_app_delegate()
1212
cls = @ccall objc_allocateClassPair(a"NSResponder"cls::ID, "WebviewAppDelegate"::Cstring, 0::Int)::ID
1313
@ccall class_addProtocol(
1414
cls::ID,
@@ -19,34 +19,56 @@ function create_app_delegate(w::Webview)
1919
a"applicationShouldTerminateAfterLastWindowClosed:"sel::SEL,
2020
@cfunction(
2121
(self, _2, _3) -> begin
22-
w = get_associated_webview(self)
23-
terminate(w)
22+
_terminate()
2423
false
2524
end,
2625
Bool, (ID, SEL, ID)
2726
)::Ptr{Cvoid},
2827
"c@:@"::Cstring
2928
)::Bool
30-
if w.parent_window C_NULL
31-
@ccall class_addMethod(
32-
cls::ID,
33-
a"applicationDidFinishLaunching:"sel::SEL,
34-
@cfunction(
35-
(self, _, notification) -> begin
36-
app = @msg_send ID notification a"object"sel
37-
w = get_associated_webview(self)
38-
on_application_did_finish_launching(w, self, app)
39-
end,
40-
Cvoid, (ID, SEL, ID)
41-
)::Ptr{Cvoid},
42-
"v@:@"::Cstring
43-
)::Bool
44-
end
4529
@ccall objc_registerClassPair(cls::ID)::Ptr{Cvoid}
4630
@msg_send ID cls a"new"sel
4731
end
4832

49-
function create_webkit_ui_delegate()
33+
function get_window_delegate_class()
34+
cls = @ccall objc_getClass("WebviewWindowDelegate"::Cstring)::ID
35+
cls == C_NULL || return cls
36+
cls = @ccall objc_allocateClassPair(a"NSResponder"cls::ID, "WebviewWindowDelegate"::Cstring, 0::Int)::ID
37+
@ccall class_addProtocol(
38+
cls::ID,
39+
(@ccall objc_getProtocol("NSWindowDelegate"::Cstring)::ID)::ID
40+
)::Bool
41+
@ccall class_addMethod(
42+
cls::ID,
43+
a"windowWillClose:"sel::SEL,
44+
@cfunction(
45+
(self, _2, _3) -> begin
46+
w = get_associated_webview(self)
47+
window = window_handle(w)
48+
Webviews.on_window_close(window)
49+
end,
50+
Cvoid, (ID, SEL, ID)
51+
)::Ptr{Cvoid},
52+
"v@:@"::Cstring
53+
)::Bool
54+
@ccall objc_registerClassPair(cls::ID)::Cvoid
55+
cls
56+
end
57+
function create_window_delegate(w::Webview)
58+
cls = get_window_delegate_class()
59+
instance = @msg_send ID cls a"new"sel
60+
@ccall objc_setAssociatedObject(
61+
instance::ID,
62+
ASSOCIATED_KEY::Cstring,
63+
pointer_from_objref(w)::ID,
64+
0::UInt # OBJC_ASSOCIATION_ASSIGN
65+
)::ID
66+
instance
67+
end
68+
69+
function get_webkit_ui_delegate_class()
70+
cls = @ccall objc_getClass("WebkitUIDelegate"::Cstring)::ID
71+
cls == C_NULL || return cls
5072
cls = @ccall objc_allocateClassPair(a"NSObject"cls::ID, "WebkitUIDelegate"::Cstring, 0::Int)::ID
5173
@ccall class_addProtocol(
5274
cls::ID,
@@ -83,10 +105,16 @@ function create_webkit_ui_delegate()
83105
"v@:@@@@"::Cstring
84106
)::Bool
85107
@ccall objc_registerClassPair(cls::ID)::Cvoid
108+
cls
109+
end
110+
function create_webkit_ui_delegate()
111+
cls = get_webkit_ui_delegate_class()
86112
@msg_send ID cls a"new"sel
87113
end
88114

89-
function create_script_message_handler(w::Webview)
115+
function get_script_message_handler_class()
116+
cls = @ccall objc_getClass("WebkitScriptMessageHandler"::Cstring)::ID
117+
cls == C_NULL || return cls
90118
cls = @ccall objc_allocateClassPair(a"NSResponder"cls::ID, "WebkitScriptMessageHandler"::Cstring, 0::Int)::ID
91119
@ccall class_addProtocol(
92120
cls::ID,
@@ -110,6 +138,10 @@ function create_script_message_handler(w::Webview)
110138
"v@:@@"::Cstring
111139
)::Bool
112140
@ccall objc_registerClassPair(cls::ID)::Cvoid
141+
cls
142+
end
143+
function create_script_message_handler(w::Webview)
144+
cls = get_script_message_handler_class()
113145
instance = @msg_send ID cls a"new"sel
114146
@ccall objc_setAssociatedObject(
115147
instance::ID,
@@ -129,7 +161,7 @@ function is_app_bundled()
129161
bundled = @msg_send Bool bundle_path a"hasSuffix:"sel a".app"str
130162
end
131163

132-
function on_application_did_finish_launching(w::Webview, _self::ID, app::ID)
164+
function on_application_did_finish_launching(w::Webview, app::ID)
133165
if w.parent_window C_NULL
134166
@msg_send Cvoid app a"stop:"sel C_NULL
135167
end
@@ -152,6 +184,7 @@ function on_application_did_finish_launching(w::Webview, _self::ID, app::ID)
152184
else
153185
w.parent_window
154186
end
187+
create_window_delegate(w)
155188

156189
# Webview
157190
config = w.config = @msg_send ID a"WKWebViewConfiguration"cls a"new"sel

0 commit comments

Comments
 (0)