Skip to content

WebView basics

Wx::WEB::WebView embeds a full browser engine in your desktop app. On macOS and Linux it uses WebKit; on Windows it uses the Edge WebView2 runtime. The result is a widget that can render any modern web page, execute JavaScript, load external resources, and display content indistinguishable from a browser tab.

This is categorically different from Wx::HTML::HtmlWindow. HtmlWindow is a lightweight renderer supporting a useful subset of HTML. WebView is a full browser — everything that works in Chrome or Safari works in a WebView.

Here’s our first sample

basic webview

Creating a WebView

The widget lives in the Wx::WEB namespace:

1
@webview = Wx::WEB::WebView.new(parent, Wx::ID_ANY, 'about:blank')

Pass 'about:blank' as the initial URL — loading content before the WebView is fully initialised can cause issues on some platforms.

Three ways to load content

Approach 1 — Inline HTML with set_page

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
html = <<~HTML
  <!DOCTYPE html>
  <html>
  <body>
    <h1>Hello from Ruby</h1>
  </body>
  </html>
HTML

@webview.set_page(html, '')

set_page(html, base_url) loads an HTML string directly. The second argument is a base URL used to resolve relative resources — pass '' for fully self-contained pages. This is the simplest approach and works well for pages with no external dependencies.

Approach 2 — CDN-hosted libraries

Include third-party JavaScript libraries via CDN script tags:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
html = <<~HTML
  <!DOCTYPE html>
  <html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
  </head>
  <body>
    <canvas id="chart"></canvas>
    <script>
      // Chart.js is available here
      new Chart(document.getElementById('chart'), { ... });
    </script>
  </body>
  </html>
HTML

@webview.set_page(html, '')

The WebView fetches the library over the network at page load time. This requires internet access but keeps your app small — no library files to bundle. Use this approach whenever network access is available.

Approach 3 — Base64 data URI

Encode the complete HTML page as base64 and load via load_url:

1
2
encoded = Base64.strict_encode64(html)
@webview.load_url("data:text/html;base64,#{encoded}")

This is the most reliable cross-platform approach. set_page has known issues with some WebKit versions — inline scripts may not execute consistently. load_url with a base64 data URI works identically on macOS, Windows, and Linux.

Rule of thumb: Use set_page for simple static content. Use load_url with base64 encoding whenever the page contains JavaScript that must execute reliably.

For local asset files (Leaflet.js, custom CSS), encode them as base64 and inline them as data URIs in the HTML:

1
2
3
4
5
6
7
leaflet_js  = Base64.strict_encode64(File.read('assets/leaflet.js'))
leaflet_css = Base64.strict_encode64(File.read('assets/leaflet.css'))

html = <<~HTML
  <link rel="stylesheet" href="data:text/css;base64,#{leaflet_css}">
  <script src="data:text/javascript;base64,#{leaflet_js}"></script>
HTML

This eliminates all file path issues and makes your app fully self-contained.

WebView events

Register event handlers before loading content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Fires when a page finishes loading
@webview.evt_webview_loaded(@webview.id) do
  next if @webview.current_url == 'about:blank'
  # page is fully loaded — safe to run_script here
end

# Fires when navigation begins
@webview.evt_webview_navigating(@webview.id) do |event|
  set_status_text("Loading #{event.url}...")
end

# Fires when navigation completes (URL confirmed)
@webview.evt_webview_navigated(@webview.id) do |event|
  @url_field.value = event.url
end

# Fires on load errors
@webview.evt_webview_error(@webview.id) do |event|
  set_status_text("Error: #{event.string}")
end

# Fires when the page title changes
@webview.evt_webview_title_changed(@webview.id) do |event|
  self.title = event.string unless event.string.empty?
end

Always check for about:blank in evt_webview_loaded — it fires for the initial blank page too.

Running JavaScript from Ruby

run_script executes JavaScript in the WebView and returns the result:

1
2
3
4
5
# Execute and ignore result
@webview.run_script("document.body.style.background = 'red'")

# Execute and capture result
result = @webview.run_script("document.title")

run_script should only be called after evt_webview_loaded fires — never during page load. It may produce log noise on some platforms; suppress it by temporarily lowering the log level:

1
2
3
4
5
6
7
8
9
def run_script_silent(js)
  old_level = Wx::Log.get_log_level
  Wx::Log.set_log_level(0)
  result = @webview.run_script(js)
  Wx::Log.set_log_level(old_level)
  result
rescue
  Wx::Log.set_log_level(old_level) rescue nil
end

This pattern is encapsulated in the JsBridge class introduced in lesson 5.2.

Upgrading the markdown editor

In lesson 4.5 we built a markdown editor using Wx::HTML::HtmlWindow for the preview. Replacing it with WebView is a one-line change in preview_panel.rb — the rest of the app is unchanged:

1
2
3
4
5
6
7
8
9
# Before (lesson 4.5):
@html = Wx::HTML::HtmlWindow.new(self)
@html.set_standard_fonts(13)
@html.set_page(html)

# After (WebView):
@webview = Wx::WEB::WebView.new(self, Wx::ID_ANY, 'about:blank')
encoded = Base64.strict_encode64(wrap_html(html))
@webview.load_url("data:text/html;base64,#{encoded}")

The preview immediately improves — proper CSS, better typography, and code blocks that look like code blocks. The capstone in lesson 5.5 shows the full upgrade including syntax highlighting.

See webview_demo.rb for a working demonstration of all three loading approaches with a live run_script test panel.

Download webview_demo.rb

What to take forward

  • Namespace is Wx::WEB::WebView
  • Start with 'about:blank', load content after the WebView is initialised
  • set_page(html, '') for simple content; load_url("data:text/html;base64,...") for reliable script execution
  • CDN script tags for third-party libraries when network is available; base64 data URIs for offline/bundled assets
  • Always check current_url == 'about:blank' in evt_webview_loaded
  • run_script only after evt_webview_loaded — never during page load

Previous: Module 4 | Next: The JavaScript bridge