From 67b6eb83005c2a9f29a02fe923adcc6833a32b61 Mon Sep 17 00:00:00 2001 From: Christian Dywan Date: Fri, 8 Feb 2019 18:43:26 +0100 Subject: Initial support for cross-browser Web Extension API (#250) * Initial support for cross-browser Web Extension API * Json-GLib is a introduced as a new dependency to read manifest.json * The README contains a new section to summarize the Web Extension API * Web extension support is implemented as a Peas plugin * Web extensions can be built-in, system-wide or user-installed * The Javascript interface is added to the core GLib.Resource * Midori.Tab shares a WebKit.UserContentManager per WebKit.WebContext Fixes: #244 * Only insert loaded web extensions and employ a delay installing the API Otherwise opening a new window with many extensions would result in significant delay. --- .circleci/config.yml | 2 +- CMakeLists.txt | 3 +- README.md | 44 ++++- core/CMakeLists.txt | 1 + core/tab.vala | 10 +- data/web-extension-api.js | 24 +++ extensions/CMakeLists.txt | 6 + extensions/web-extensions.plugin.in | 5 + extensions/web-extensions.vala | 372 ++++++++++++++++++++++++++++++++++++ gresource.xml | 1 + po/POTFILES.in | 2 + vapi/javascriptcoregtk-4.0.vapi | 4 +- vapi/webkit2gtk-4.0.vapi | 6 +- 13 files changed, 470 insertions(+), 10 deletions(-) create mode 100644 data/web-extension-api.js create mode 100644 extensions/web-extensions.plugin.in create mode 100644 extensions/web-extensions.vala diff --git a/.circleci/config.yml b/.circleci/config.yml index 2a987277..0a625924 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ jobs: name: Install dependencies command: | apt-get update - apt-get install -y cmake xvfb valac libwebkit2gtk-4.0-dev libsoup-gnome2.4-dev libgcr-3-dev libpeas-dev libsqlite3-dev intltool libxml2-utils + apt-get install -y cmake xvfb valac libwebkit2gtk-4.0-dev libsoup-gnome2.4-dev libgcr-3-dev libpeas-dev libsqlite3-dev libjson-glib-dev intltool libxml2-utils - run: name: Build command: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c5f07db..c1884e8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,8 +54,9 @@ pkg_check_modules(DEPS_GTK REQUIRED webkit2gtk-4.0>=2.16.6 gcr-ui-3>=2.32 libpeas-gtk-1.0 + json-glib-1.0>=0.12 ) -set(PKGS ${PKGS} gtk+-3.0 libsoup-2.4 gcr-ui-3 libpeas-gtk-1.0) +set(PKGS ${PKGS} gtk+-3.0 libsoup-2.4 gcr-ui-3 libpeas-gtk-1.0 json-glib-1.0) set(EXTRA_VAPIS ${CMAKE_SOURCE_DIR}/vapi/config.vapi ${CMAKE_SOURCE_DIR}/vapi/webkit2gtk-4.0.vapi diff --git a/README.md b/README.md index 2c534428..26a0b772 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Midori is a lightweight yet powerful web browser which runs just as well on litt * Customizable side panels. * User scripts and styles a la Greasemonkey. * Web developer tools powered by WebKit. +* Cross-browser extensions compatible with Chrome, Firefox, Opera and Vivaldi Please report comments, suggestions and bugs to: https://github.com/midori-browser/core/issues @@ -64,14 +65,15 @@ You can opt-in for the [beta release on the Play Store](https://play.google.com/ * [Vala](https://wiki.gnome.org/Projects/Vala) 0.30 * GCR 2.32 * Peas +* JSON-Glib 0.12 Install dependencies on Astian OS, Ubuntu, Debian or other Debian-based distros: - sudo apt install cmake valac libwebkit2gtk-4.0-dev libgcr-3-dev libpeas-dev libsqlite3-dev intltool libxml2-utils + sudo apt install cmake valac libwebkit2gtk-4.0-dev libgcr-3-dev libpeas-dev libsqlite3-dev libjson-glib-dev intltool libxml2-utils Install dependencies on openSUSE: - sudo zypper in cmake vala gcc webkit2gtk3-devel libgcr-devel libpeas-devel sqlite3-devel fdupes gettext-tools intltool libxml2-devel + sudo zypper in cmake vala gcc webkit2gtk3-devel libgcr-devel libpeas-devel sqlite3-devel json-glib-devel fdupes gettext-tools intltool libxml2-devel Use CMake to build Midori: @@ -247,6 +249,43 @@ Push your branch and **propose it for merging into master**. This will automatically request a **review from other developers** who can then comment on it and provide feedback. +# Extensions + +## Cross-browser web extensions + +The following API specification is supported by Midori: + + manifest.json + name + version + description + background: + page: *.html + scripts: + - *.js + browser_action: + default_popup: *.html + default_icon: *.png + default_title + content_scripts: + js: + - *.js + css: + - *.css + manifest_version: 2 + + *.js + browser (chrome) + tabs + create + - url: uri + executeScript + - code: string + notifications + create + - title: string + message: string + # Jargon * **freeze**: a period of bug fixes eg. 4/2 cycle means 4 weeks of features and 2 weeks of stabilization @@ -254,6 +293,7 @@ This will automatically request a **review from other developers** who can then * **ninja**: an internal tab, usually empty label, used for taking screenshots * **fortress**: user of an ancient release like 0.4.3 as found on Raspberry Pie, Debian, Ubuntu * **katze, sokoke, tabby**: legacy API names and coincidentally cat breeds +* web extension: a cross-browser extension (plugin) - or in a webkit context, the multi-process api # Midori for Android diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 23761abd..0fb9d09f 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -48,6 +48,7 @@ add_custom_command(OUTPUT resources.c ${CMAKE_SOURCE_DIR}/gresource.xml DEPENDS ${CMAKE_SOURCE_DIR}/gresource.xml DEPENDS ${CMAKE_SOURCE_DIR}/data/gtk3.css + DEPENDS ${CMAKE_SOURCE_DIR}/data/web-extension-api.js DEPENDS ${UI_FILES} ) diff --git a/core/tab.vala b/core/tab.vala index e8197d6c..fd574827 100644 --- a/core/tab.vala +++ b/core/tab.vala @@ -59,7 +59,15 @@ namespace Midori { public Tab (Tab? related, WebKit.WebContext web_context, string? uri = null, string? title = null) { - Object (related_view: related, web_context: web_context, visible: true); + + // One content manager per web context + var content = web_context.get_data ("user-content-manager"); + if (content == null) { + content = new WebKit.UserContentManager (); + web_context.set_data ("user-content-manager", content); + } + + Object (related_view: related, web_context: web_context, user_content_manager: content, visible: true); var settings = get_settings (); settings.user_agent += " %s".printf (Config.CORE_USER_AGENT_VERSION); diff --git a/data/web-extension-api.js b/data/web-extension-api.js new file mode 100644 index 00000000..83e85bd3 --- /dev/null +++ b/data/web-extension-api.js @@ -0,0 +1,24 @@ +// Promise-based message handler +var promises = []; +var last_promise = 0; +var m = function (fn, args, cb) { + var promise = new Promise (function (resolve, reject) { + window.webkit.messageHandlers.midori.postMessage ({fn: fn, args: args, promise: last_promise}); + last_promise = promises.push({resolve: resolve, reject: reject}); + }); + return promise; +} + +// Browser API +window.browser = { + tabs: { + create: function (args, cb) { return m ('tabs.create', args, cb); }, + executeScript: function (args, cb) { return m ('tabs.executeScript', args, cb); }, + }, + notifications: { + create: function (args, cb) { return m ('notifications.create', args, cb); }, + } +} + +// Compatibility with Chrome +window.chrome = window.browser; diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index 8537e920..f2b63686 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -15,6 +15,12 @@ foreach(UNIT_SRC ${EXTENSIONS}) string(FIND ${UNIT_SRC} "." UNIT_EXTENSION) if (UNIT_EXTENSION EQUAL -1) set(UNIT ${UNIT_SRC}) + file(GLOB MANIFEST RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${UNIT_SRC}/manifest.json") + # Web Extension + if (MANIFEST MATCHES "(manifest.json)$") + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/${UNIT} ${CMAKE_CURRENT_BINARY_DIR}/${UNIT}) + continue() + endif () file(GLOB UNIT_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${UNIT_SRC}/*.vala") elseif (${UNIT_SRC} MATCHES "(.vala)$") string(REPLACE ".vala" "" UNIT ${UNIT_SRC}) diff --git a/extensions/web-extensions.plugin.in b/extensions/web-extensions.plugin.in new file mode 100644 index 00000000..ad759848 --- /dev/null +++ b/extensions/web-extensions.plugin.in @@ -0,0 +1,5 @@ +[Plugin] +Module=web-extensions +IAge=3 +Builtin=true +Name=Web Extensions diff --git a/extensions/web-extensions.vala b/extensions/web-extensions.vala new file mode 100644 index 00000000..155248d6 --- /dev/null +++ b/extensions/web-extensions.vala @@ -0,0 +1,372 @@ +/* + Copyright (C) 2019 Christian Dywan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See the file COPYING for the full license text. +*/ + +namespace WebExtension { + public class Extension : Object { + public File file { get; protected set; } + public string name { get; set; } + public string description { get; set; } + public string? background_page { get; owned set; } + public List background_scripts { get; owned set; } + public List content_scripts { get; owned set; } + public List content_styles { get; owned set; } + public Action? browser_action { get; set; } + + public Extension (File file) { + Object (file: file, name: file.get_basename ()); + } + } + + public class Action : Object { + public string? icon { get; protected set; } + public string? title { get; protected set; } + public string? popup { get; protected set; } + + public Action (string? icon, string? title, string? popup) { + Object (icon: icon, title: title, popup: popup); + } + } + + public class ExtensionManager : Object { + static ExtensionManager? _default = null; + HashTable extensions; + + public delegate void ExtensionManagerForeachFunc (Extension extension); + public void @foreach (ExtensionManagerForeachFunc func) { + extensions.foreach ((key, value) => { + func (value); + }); + } + + // Note: Can't use the actual type here + // parameter 1 of type 'WebExtensionExtension' for signal + // "WebExtensionExtensionManager::extension_added" is not a value type + public signal void extension_added (Object extension); + + public static ExtensionManager get_default () { + if (_default == null) { + _default = new ExtensionManager (); + _default.extensions = new HashTable (str_hash, str_equal); + } + return _default; + } + + public async void load_from_folder (WebKit.UserContentManager content, File folder) throws Error { + debug ("Load web extensions from %s", folder.get_path ()); + var enumerator = yield folder.enumerate_children_async (FileAttribute.STANDARD_NAME, 0); + FileInfo info; + while ((info = enumerator.next_file ()) != null) { + var file = folder.get_child (info.get_name ()); + string id = Checksum.compute_for_string (ChecksumType.MD5, file.get_path ()); + var extension = extensions.lookup (id); + if (extension == null) { + extension = new Extension (file); + + // If we find a manifest, this is a web extension + var manifest_file = file.get_child ("manifest.json"); + if (!manifest_file.query_exists ()) { + continue; + } + + try { + var json = new Json.Parser (); + yield json.load_from_stream_async (new DataInputStream (manifest_file.read ())); + var manifest = json.get_root ().get_object (); + if (manifest.has_member ("name")) { + extension.name = manifest.get_string_member ("name"); + } + + if (manifest.has_member ("background")) { + var background = manifest.get_object_member ("background"); + if (background != null) { + if (background.has_member ("page")) { + extension.background_page = background.get_string_member ("page"); + } + + if (background.has_member ("scripts")) { + foreach (var element in background.get_array_member ("scripts").get_elements ()) { + extension.background_scripts.append (element.get_string ()); + } + } + } + } + + if (manifest.has_member ("browser_action")) { + var action = manifest.get_object_member ("browser_action"); + if (action != null) { + extension.browser_action = new Action ( + action.has_member ("default_icon") ? action.get_string_member ("default_icon") : null, + action.has_member ("default_title") ? action.get_string_member ("default_title") : null, + action.has_member ("default_popup") ? action.get_string_member ("default_popup") : null); + } + } + + if (manifest.has_member ("content_scripts")) { + var content_scripts = manifest.get_object_member ("content_scripts"); + if (content_scripts != null && content_scripts.has_member ("js")) { + foreach (var element in content_scripts.get_array_member ("js").get_elements ()) { + extension.content_scripts.append (element.get_string ()); + } + } + + if (content_scripts != null && content_scripts.has_member ("css")) { + foreach (var element in content_scripts.get_array_member ("css").get_elements ()) { + extension.content_styles.append (element.get_string ()); + } + } + } + + debug ("Loaded %s from %s", extension.name, file.get_path ()); + extensions.insert (id, extension); + extension_added (extension); + } catch (Error error) { + warning ("Failed to load extension '%s': %s\n", extension.name, error.message); + } + } + + foreach (var filename in extension.content_scripts) { + uint8[] script; + yield file.get_child (filename).load_contents_async (null, out script, null); + content.add_script (new WebKit.UserScript ((string)script, + WebKit.UserContentInjectedFrames.TOP_FRAME, + WebKit.UserScriptInjectionTime.END, + null, null)); + } + foreach (var filename in extension.content_styles) { + uint8[] stylesheet; + yield file.get_child (filename).load_contents_async (null, out stylesheet, null); + content.add_style_sheet (new WebKit.UserStyleSheet ((string)stylesheet, + WebKit.UserContentInjectedFrames.TOP_FRAME, + WebKit.UserStyleLevel.USER, + null, null)); + } + } + } + + Midori.App app { get { return Application.get_default () as Midori.App; } } + Midori.Browser browser { get { return app.active_window as Midori.Browser; } } + + void web_extension_message_received (WebKit.WebView web_view, WebKit.JavascriptResult result) { + unowned JS.GlobalContext context = result.get_global_context (); + unowned JS.Value value = result.get_value (); + if (value.is_object (context)) { + var object = value.to_object (context); + string? fn = js_to_string (context, object.get_property (context, new JS.String.create_with_utf8_cstring ("fn"))); + if (fn != null && fn.has_prefix ("tabs.create")) { + var args = object.get_property (context, new JS.String.create_with_utf8_cstring ("args")).to_object (context); + string? url = js_to_string (context, args.get_property (context, new JS.String.create_with_utf8_cstring ("url"))); + var tab = new Midori.Tab (null, browser.tab.web_context, url); + browser.add (tab); + var promise = object.get_property (context, new JS.String.create_with_utf8_cstring ("promise")).to_number (context); + debug ("Calling back to promise #%.f".printf (promise)); + web_view.run_javascript.begin ("promises[%.f].resolve({id:%s});".printf (promise, tab.id)); + } else if (fn != null && fn.has_prefix ("tabs.executeScript")) { + var args = object.get_property (context, new JS.String.create_with_utf8_cstring ("args")).to_object (context); + string? results = null; + string? code = js_to_string (context, args.get_property (context, new JS.String.create_with_utf8_cstring ("code"))); + if (code != null) { + results = "[true]"; + browser.tab.run_javascript.begin (code); + } + var promise = object.get_property (context, new JS.String.create_with_utf8_cstring ("promise")).to_number (context); + debug ("Calling back to promise #%.f".printf (promise)); + web_view.run_javascript.begin ("promises[%.f].resolve(%s);".printf (promise, results ?? "[undefined]")); + } else if (fn != null && fn.has_prefix ("notifications.create")) { + var args = object.get_property (context, new JS.String.create_with_utf8_cstring ("args")).to_object (context); + string? message = js_to_string (context, args.get_property (context, new JS.String.create_with_utf8_cstring ("message"))); + string? title = js_to_string (context, args.get_property (context, new JS.String.create_with_utf8_cstring ("title"))); + var notification = new Notification (title); + notification.set_body (message); + // Use per-extension ID to avoid collisions + string extension_uri = web_view.uri; + app.send_notification (extension_uri, notification); + } else { + warning ("Unsupported Web Extension API: %s", fn); + } + } else { + warning ("Unexpected non-object value posted to Web Extension API: %s", js_to_string (context, value)); + } + } + + public void install_api (WebKit.WebView web_view) { + web_view.get_settings ().enable_write_console_messages_to_stdout = true; // XXX + + var content = web_view.get_user_content_manager (); + if (content.register_script_message_handler ("midori")) { + content.script_message_received.connect ((result) => { + web_extension_message_received (web_view, result); + }); + try { + string script = (string)resources_lookup_data ("/data/web-extension-api.js", + ResourceLookupFlags.NONE).get_data (); + content.add_script (new WebKit.UserScript ((string)script, + WebKit.UserContentInjectedFrames.ALL_FRAMES, + WebKit.UserScriptInjectionTime.START, + null, null)); + + } catch (Error error) { + critical ("Failed to setup WebExtension API: %s", error.message); + } + } else { + warning ("Failed to install WebExtension API handler"); + } + + } + } + + public class WebView : WebKit.WebView { + public WebView (Extension extension, string? uri = null) { + Object (visible: true); + + var manager = ExtensionManager.get_default (); + manager.install_api (this); + + if (uri != null) { + load_uri (extension.file.get_child (uri).get_uri ()); + } else { + load_html ("", extension.file.get_uri ()); + } + } + + public override bool context_menu (WebKit.ContextMenu menu, + Gdk.Event event, WebKit.HitTestResult hit) { + + if (hit.context_is_editable ()) { + return false; + } + + return true; + } + + public override void close () { + destroy (); + } + + public override bool web_process_crashed () { + load_alternate_html ("", uri, uri); + return true; + } + } + + public class Button : Gtk.MenuButton { + public Button (Extension extension) { + tooltip_text = extension.browser_action.title ?? extension.name; + visible = true; + focus_on_click = false; + var icon = new Gtk.Image.from_icon_name ("midori-symbolic", Gtk.IconSize.BUTTON); + icon.use_fallback = true; + icon.visible = true; + if (extension.browser_action.icon != null) { + debug ("Icon for %s: %s\n", + extension.name, + extension.file.get_child (extension.browser_action.icon).get_path ()); + // Ensure the icon fits the size of a button in the toolbar + int icon_width = 16, icon_height = 16; + Gtk.icon_size_lookup (Gtk.IconSize.BUTTON, out icon_width, out icon_height); + // Take scale factor into account + icon_width *= scale_factor; + icon_height *= scale_factor; + try { + string filename = extension.file.get_child (extension.browser_action.icon).get_path (); + icon.pixbuf = new Gdk.Pixbuf.from_file_at_scale (filename, icon_width, icon_height, true); + } catch (Error error) { + warning ("Failed to set icon for %s: %s", extension.name, error.message); + } + } + if (extension.browser_action.popup != null) { + popover = new Gtk.Popover (this); + popover.add (new WebView (extension, extension.browser_action.popup)); + } + add (icon); + } + } + + static string? js_to_string (JS.GlobalContext context, JS.Value value) { + if (!value.is_string (context)) { + return null; + } + var str = value.to_string_copy (context); + uint8[] buffer = new uint8[str.get_maximum_utf8_cstring_size ()]; + str.get_utf8_cstring (buffer); + return ((string)buffer); + } + + public class Browser : Object, Midori.BrowserActivatable { + public Midori.Browser browser { owned get; set; } + + async void install_extension (Extension extension) throws Error { + if (extension.browser_action != null) { + browser.add_button (new Button (extension as Extension)); + } + + // Employ a delay to avoid delaying startup with many extensions + uint src = Timeout.add (500, install_extension.callback); + yield; + Source.remove (src); + + // Insert the background page in the browser, as a hidden widget + var background = new WebView (extension, extension.background_page); + (((Gtk.Container)browser.get_child ())).add (background); + + foreach (var filename in extension.background_scripts) { + uint8[] script; + yield extension.file.get_child (filename).load_contents_async (null, out script, null); + background.get_user_content_manager ().add_script (new WebKit.UserScript ((string)script, + WebKit.UserContentInjectedFrames.TOP_FRAME, + WebKit.UserScriptInjectionTime.END, + null, null)); + } + } + + public void activate () { + if (browser.is_locked) { + return; + } + + var manager = ExtensionManager.get_default (); + manager.extension_added.connect ((extension) => { + install_extension.begin ((Extension)extension); + }); + manager.foreach ((extension) => { + install_extension.begin ((Extension)extension); + }); + + browser.tabs.add.connect (tab_added); + if (browser.tab != null) { + tab_added (browser.tab); + } + } + + void tab_added (Gtk.Widget widget) { + browser.tabs.add.disconnect (tab_added); + + var manager = ExtensionManager.get_default (); + var tab = widget as Midori.Tab; + + var content = tab.get_user_content_manager (); + // Try and load plugins from build folder + var builtin_path = ((Midori.App)Application.get_default ()).exec_path.get_parent ().get_child ("extensions"); + manager.load_from_folder.begin (content, builtin_path); + // System-wide plugins + manager.load_from_folder.begin (content, File.new_for_path (Config.PLUGINDIR)); + // Plugins installed by the user + string user_path = Path.build_path (Path.DIR_SEPARATOR_S, + Environment.get_user_data_dir (), Config.PROJECT_NAME, "extensions"); + manager.load_from_folder.begin (content, File.new_for_path (user_path)); + } + } +} + +[ModuleInit] +public void peas_register_types(TypeModule module) { + ((Peas.ObjectModule)module).register_extension_type ( + typeof (Midori.BrowserActivatable), typeof (WebExtension.Browser)); +} diff --git a/gresource.xml b/gresource.xml index 92418a7c..3d41bdd5 100644 --- a/gresource.xml +++ b/gresource.xml @@ -16,6 +16,7 @@ data/speed-dial.html data/gtk3.css data/logo-shade.svg + data/web-extension-api.js data/bookmarks/Create.sql data/history/Create.sql data/history/Day.sql diff --git a/po/POTFILES.in b/po/POTFILES.in index 1ed62a84..9a4fb710 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -43,6 +43,8 @@ extensions/colorful-tabs.plugin.in extensions/colorful-tabs.vala extensions/session.plugin.in extensions/session.vala +extensions/web-extensions.plugin.in +extensions/web-extensions.vala ui/bookmarks-button.ui ui/browser.ui ui/clear-private-data.ui diff --git a/vapi/javascriptcoregtk-4.0.vapi b/vapi/javascriptcoregtk-4.0.vapi index c937cae1..05be292b 100644 --- a/vapi/javascriptcoregtk-4.0.vapi +++ b/vapi/javascriptcoregtk-4.0.vapi @@ -38,7 +38,7 @@ namespace JS { [CCode (cname = "JSObjectCallAsFunction", instance_pos = 1.1)] public JS.Value call_as_function (JS.Context ctx, JS.Object? this_object, [CCode (array_length_pos = 2.5)] JS.Value[]? arguments, out JS.Value? exception); [CCode (cname = "JSObjectGetProperty", instance_pos = 1.1)] - public JS.Value get_property (JS.Context ctx, JS.String property_name, out JS.Value? exception); + public JS.Value get_property (JS.Context ctx, JS.String property_name, out JS.Value? exception = null); [CCode (cname = "JSObjectHasProperty", instance_pos = 1.1)] public bool has_property (JS.Context ctx, JS.String property_name); [CCode (cname = "JSObjectMakeFunction")] @@ -54,7 +54,7 @@ namespace JS { [CCode (cname = "JSStringGetMaximumUTF8CStringSize")] public size_t get_maximum_utf8_cstring_size (); [CCode (cname = "JSStringGetUTF8CString")] - public size_t get_utf8_cstring ([CCode (array_length_type = "gsize")] ref uint8[] buffer); + public size_t get_utf8_cstring ([CCode (array_length_type = "gsize")] uint8[] buffer); [CCode (cname = "JSStringIsEqual")] public bool is_equal (JS.String b); [CCode (cname = "JSStringIsEqualToUTF8CString")] diff --git a/vapi/webkit2gtk-4.0.vapi b/vapi/webkit2gtk-4.0.vapi index 8bdeac94..ad16d717 100644 --- a/vapi/webkit2gtk-4.0.vapi +++ b/vapi/webkit2gtk-4.0.vapi @@ -809,15 +809,15 @@ namespace WebKit { public bool is_editable (); public void load_alternate_html (string content, string content_uri, string? base_uri); public void load_bytes (GLib.Bytes bytes, string? mime_type, string? encoding, string? base_uri); - public void load_html (string content, string? base_uri); + public void load_html (string content, string? base_uri = null); public void load_plain_text (string plain_text); public void load_request (WebKit.URIRequest request); public void load_uri (string uri); public void reload (); public void reload_bypass_cache (); public void restore_session_state (WebKit.WebViewSessionState state); - public async WebKit.JavascriptResult run_javascript (string script, GLib.Cancellable? cancellable) throws GLib.Error; - public async WebKit.JavascriptResult run_javascript_from_gresource (string resource, GLib.Cancellable? cancellable) throws GLib.Error; + public async WebKit.JavascriptResult run_javascript (string script, GLib.Cancellable? cancellable = null) throws GLib.Error; + public async WebKit.JavascriptResult run_javascript_from_gresource (string resource, GLib.Cancellable? cancellable = null) throws GLib.Error; public async GLib.InputStream save (WebKit.SaveMode save_mode, GLib.Cancellable? cancellable) throws GLib.Error; public async bool save_to_file (GLib.File file, WebKit.SaveMode save_mode, GLib.Cancellable? cancellable) throws GLib.Error; public void set_background_color (Gdk.RGBA rgba); -- cgit v1.2.1