645 lines
26 KiB
Diff
645 lines
26 KiB
Diff
From ff565bc6efc83badbecfb48d2fbb457f4d2f681c Mon Sep 17 00:00:00 2001
|
|
From: Michael Gratton <mike@vee.net>
|
|
Date: Thu, 27 Aug 2020 12:12:22 +1000
|
|
Subject: [PATCH 070/124] Components.WebView: Convert to using messages for JS
|
|
method invocation
|
|
|
|
Use WebKitGTK UserMessage objects for invoking JS methods rather than
|
|
serialising to JS strings and running those. This is possibly slightly
|
|
less efficient, but removes the onus on serialising to and parsing from
|
|
JS and once switched over from message handlers to UserMessage objects
|
|
will be using a single uniform IPC interface for both.
|
|
---
|
|
.../components/components-web-view.vala | 97 +++++++++++++++++--
|
|
src/client/composer/composer-web-view.vala | 67 ++++++-------
|
|
src/client/composer/composer-widget.vala | 19 ++--
|
|
.../conversation-web-view.vala | 15 ++-
|
|
src/client/util/util-js.vala | 36 ++++---
|
|
.../web-process/web-process-extension.vala | 53 ++++++++++
|
|
test/js/components-page-state-test.vala | 45 +++++++++
|
|
ui/components-web-view.js | 10 ++
|
|
8 files changed, 268 insertions(+), 74 deletions(-)
|
|
|
|
diff --git a/src/client/components/components-web-view.vala b/src/client/components/components-web-view.vala
|
|
index 4bda1c11..368b6a8d 100644
|
|
--- a/src/client/components/components-web-view.vala
|
|
+++ b/src/client/components/components-web-view.vala
|
|
@@ -370,9 +370,7 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
|
* Returns the view's content as an HTML string.
|
|
*/
|
|
public async string? get_html() throws Error {
|
|
- return Util.JS.to_string(
|
|
- yield call(Util.JS.callable("geary.getHtml"), null)
|
|
- );
|
|
+ return yield call_returning<string?>(Util.JS.callable("getHtml"), null);
|
|
}
|
|
|
|
/**
|
|
@@ -410,7 +408,7 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
|
* Load any remote images previously that were blocked.
|
|
*/
|
|
public void load_remote_images() {
|
|
- this.call.begin(Util.JS.callable("geary.loadRemoteImages"), null);
|
|
+ this.call_void.begin(Util.JS.callable("loadRemoteImages"), null);
|
|
}
|
|
|
|
/**
|
|
@@ -455,21 +453,100 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
|
|
public new async void set_editable(bool enabled,
|
|
Cancellable? cancellable)
|
|
throws Error {
|
|
- yield call(
|
|
- Util.JS.callable("geary.setEditable").bool(enabled), cancellable
|
|
+ yield call_void(
|
|
+ Util.JS.callable("setEditable").bool(enabled), cancellable
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Invokes a {@link Util.JS.Callable} on this web view.
|
|
+ *
|
|
+ * This calls the given callable on the `geary` object for the
|
|
+ * current view, any returned value are ignored.
|
|
*/
|
|
- protected async JSC.Value call(Util.JS.Callable target,
|
|
+ protected async void call_void(Util.JS.Callable target,
|
|
GLib.Cancellable? cancellable)
|
|
throws GLib.Error {
|
|
- WebKit.JavascriptResult result = yield run_javascript(
|
|
- target.to_string(), cancellable
|
|
+ yield send_message_to_page(
|
|
+ target.to_message(), cancellable
|
|
);
|
|
- return result.get_js_value();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Invokes a {@link Util.JS.Callable} on this web view.
|
|
+ *
|
|
+ * This calls the given callable on the `geary` object for the
|
|
+ * current view. The value returned by the call is returned by
|
|
+ * this method.
|
|
+ *
|
|
+ * The type parameter `T` must match the type returned by the
|
|
+ * call, else an error is thrown. Only simple nullable value types
|
|
+ * are supported for T, for more complex return types (arrays,
|
|
+ * dictionaries, etc) specify {@link GLib.Variant} for `T` and
|
|
+ * manually parse that.
|
|
+ */
|
|
+ protected async T call_returning<T>(Util.JS.Callable target,
|
|
+ GLib.Cancellable? cancellable)
|
|
+ throws GLib.Error {
|
|
+ WebKit.UserMessage? response = yield send_message_to_page(
|
|
+ target.to_message(), cancellable
|
|
+ );
|
|
+ if (response == null) {
|
|
+ throw new Util.JS.Error.TYPE(
|
|
+ "Method call did not return a value: %s", target.to_string()
|
|
+ );
|
|
+ }
|
|
+ GLib.Variant? param = response.parameters;
|
|
+ T ret_value = null;
|
|
+ var ret_type = typeof(T);
|
|
+ if (ret_type == typeof(GLib.Variant)) {
|
|
+ ret_value = param;
|
|
+ } else {
|
|
+ if (param != null && param.get_type().is_maybe()) {
|
|
+ param = param.get_maybe();
|
|
+ }
|
|
+ if (param != null) {
|
|
+ // Since these replies are coming from JS via
|
|
+ // Util.JS.value_to_variant, they will only be one of
|
|
+ // string, double, bool, array or dict
|
|
+ var param_type = param.classify();
|
|
+ if (ret_type == typeof(string) && param_type == STRING) {
|
|
+ ret_value = param.get_string();
|
|
+ } else if (ret_type == typeof(bool) && param_type == BOOLEAN) {
|
|
+ ret_value = (bool?) param.get_boolean();
|
|
+ } else if (ret_type == typeof(int) && param_type == DOUBLE) {
|
|
+ ret_value = (int?) ((int) param.get_double());
|
|
+ } else if (ret_type == typeof(short) && param_type == DOUBLE) {
|
|
+ ret_value = (short?) ((short) param.get_double());
|
|
+ } else if (ret_type == typeof(char) && param_type == DOUBLE) {
|
|
+ ret_value = (char?) ((char) param.get_double());
|
|
+ } else if (ret_type == typeof(long) && param_type == DOUBLE) {
|
|
+ ret_value = (long?) ((long) param.get_double());
|
|
+ } else if (ret_type == typeof(int64) && param_type == DOUBLE) {
|
|
+ ret_value = (int64?) ((int64) param.get_double());
|
|
+ } else if (ret_type == typeof(uint) && param_type == DOUBLE) {
|
|
+ ret_value = (uint?) ((uint) param.get_double());
|
|
+ } else if (ret_type == typeof(uchar) && param_type == DOUBLE) {
|
|
+ ret_value = (uchar?) ((uchar) param.get_double());
|
|
+ } else if (ret_type == typeof(ushort) && param_type == DOUBLE) {
|
|
+ ret_value = (ushort?) ((ushort) param.get_double());
|
|
+ } else if (ret_type == typeof(ulong) && param_type == DOUBLE) {
|
|
+ ret_value = (ulong?) ((ulong) param.get_double());
|
|
+ } else if (ret_type == typeof(uint64) && param_type == DOUBLE) {
|
|
+ ret_value = (uint64?) ((uint64) param.get_double());
|
|
+ } else if (ret_type == typeof(double) && param_type == DOUBLE) {
|
|
+ ret_value = (double?) param.get_double();
|
|
+ } else if (ret_type == typeof(float) && param_type == DOUBLE) {
|
|
+ ret_value = (float?) ((float) param.get_double());
|
|
+ } else {
|
|
+ throw new Util.JS.Error.TYPE(
|
|
+ "%s is not a supported type for %s",
|
|
+ ret_type.name(), param_type.to_string()
|
|
+ );
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return ret_value;
|
|
}
|
|
|
|
/**
|
|
diff --git a/src/client/composer/composer-web-view.vala b/src/client/composer/composer-web-view.vala
|
|
index f8ecccf6..24a2740c 100644
|
|
--- a/src/client/composer/composer-web-view.vala
|
|
+++ b/src/client/composer/composer-web-view.vala
|
|
@@ -202,8 +202,8 @@ public class Composer.WebView : Components.WebView {
|
|
* Returns the view's content as HTML without being cleaned.
|
|
*/
|
|
public async string? get_html_for_draft() throws Error {
|
|
- return Util.JS.to_string(
|
|
- yield call(Util.JS.callable("geary.getHtml").bool(false), null)
|
|
+ return yield call_returning<string?>(
|
|
+ Util.JS.callable("getHtml").bool(false), null
|
|
);
|
|
}
|
|
|
|
@@ -213,8 +213,8 @@ public class Composer.WebView : Components.WebView {
|
|
public void set_rich_text(bool enabled) {
|
|
this.is_rich_text = enabled;
|
|
if (this.is_content_loaded) {
|
|
- this.call.begin(
|
|
- Util.JS.callable("geary.setRichText").bool(enabled), null
|
|
+ this.call_void.begin(
|
|
+ Util.JS.callable("setRichText").bool(enabled), null
|
|
);
|
|
}
|
|
}
|
|
@@ -223,14 +223,14 @@ public class Composer.WebView : Components.WebView {
|
|
* Undoes the last edit operation.
|
|
*/
|
|
public void undo() {
|
|
- this.call.begin(Util.JS.callable("geary.undo"), null);
|
|
+ this.call_void.begin(Util.JS.callable("undo"), null);
|
|
}
|
|
|
|
/**
|
|
* Redoes the last undone edit operation.
|
|
*/
|
|
public void redo() {
|
|
- this.call.begin(Util.JS.callable("geary.redo"), null);
|
|
+ this.call_void.begin(Util.JS.callable("redo"), null);
|
|
}
|
|
|
|
/**
|
|
@@ -239,9 +239,9 @@ public class Composer.WebView : Components.WebView {
|
|
* Returns an id to be used to refer to the selection in
|
|
* subsequent calls.
|
|
*/
|
|
- public async string save_selection() throws Error {
|
|
- return Util.JS.to_string(
|
|
- yield call(Util.JS.callable("geary.saveSelection"), null)
|
|
+ public async string? save_selection() throws Error {
|
|
+ return yield call_returning<string?>(
|
|
+ Util.JS.callable("saveSelection"), null
|
|
);
|
|
}
|
|
|
|
@@ -249,9 +249,7 @@ public class Composer.WebView : Components.WebView {
|
|
* Removes a saved selection.
|
|
*/
|
|
public void free_selection(string id) {
|
|
- this.call.begin(
|
|
- Util.JS.callable("geary.freeSelection").string(id), null
|
|
- );
|
|
+ this.call_void.begin(Util.JS.callable("freeSelection").string(id), null);
|
|
}
|
|
|
|
/**
|
|
@@ -357,9 +355,9 @@ public class Composer.WebView : Components.WebView {
|
|
* will be inserted wrapping the selection.
|
|
*/
|
|
public void insert_link(string href, string selection_id) {
|
|
- this.call.begin(
|
|
+ this.call_void.begin(
|
|
Util.JS.callable(
|
|
- "geary.insertLink"
|
|
+ "insertLink"
|
|
).string(href).string(selection_id),
|
|
null
|
|
);
|
|
@@ -373,8 +371,8 @@ public class Composer.WebView : Components.WebView {
|
|
* unlinked section.
|
|
*/
|
|
public void delete_link(string selection_id) {
|
|
- this.call.begin(
|
|
- Util.JS.callable("geary.deleteLink").string(selection_id),
|
|
+ this.call_void.begin(
|
|
+ Util.JS.callable("deleteLink").string(selection_id),
|
|
null
|
|
);
|
|
}
|
|
@@ -396,23 +394,23 @@ public class Composer.WebView : Components.WebView {
|
|
* Indents the line at the current text cursor location.
|
|
*/
|
|
public void indent_line() {
|
|
- this.call.begin(Util.JS.callable("geary.indentLine"), null);
|
|
+ this.call_void.begin(Util.JS.callable("indentLine"), null);
|
|
}
|
|
|
|
public void insert_olist() {
|
|
- this.call.begin(Util.JS.callable("geary.insertOrderedList"), null);
|
|
+ this.call_void.begin(Util.JS.callable("insertOrderedList"), null);
|
|
}
|
|
|
|
public void insert_ulist() {
|
|
- this.call.begin(Util.JS.callable("geary.insertUnorderedList"), null);
|
|
+ this.call_void.begin(Util.JS.callable("insertUnorderedList"), null);
|
|
}
|
|
|
|
/**
|
|
* Updates the signature block if it has not been deleted.
|
|
*/
|
|
public new void update_signature(string signature) {
|
|
- this.call.begin(
|
|
- Util.JS.callable("geary.updateSignature").string(signature), null
|
|
+ this.call_void.begin(
|
|
+ Util.JS.callable("updateSignature").string(signature), null
|
|
);
|
|
}
|
|
|
|
@@ -420,22 +418,21 @@ public class Composer.WebView : Components.WebView {
|
|
* Removes the quoted message (if any) from the composer.
|
|
*/
|
|
public void delete_quoted_message() {
|
|
- this.call.begin(Util.JS.callable("geary.deleteQuotedMessage"), null);
|
|
+ this.call_void.begin(Util.JS.callable("deleteQuotedMessage"), null);
|
|
}
|
|
|
|
/**
|
|
* Determines if the editor content contains an attachment keyword.
|
|
*/
|
|
- public async bool contains_attachment_keywords(string keyword_spec,
|
|
- string subject) {
|
|
+ public async bool? contains_attachment_keywords(string keyword_spec,
|
|
+ string subject) {
|
|
try {
|
|
- return Util.JS.to_bool(
|
|
- yield call(
|
|
- Util.JS.callable("geary.containsAttachmentKeyword")
|
|
- .string(keyword_spec)
|
|
- .string(subject),
|
|
- null)
|
|
- );
|
|
+ return yield call_returning<bool?>(
|
|
+ Util.JS.callable("containsAttachmentKeyword")
|
|
+ .string(keyword_spec)
|
|
+ .string(subject),
|
|
+ null
|
|
+ );
|
|
} catch (Error err) {
|
|
debug("Error checking or attachment keywords: %s", err.message);
|
|
return false;
|
|
@@ -449,7 +446,7 @@ public class Composer.WebView : Components.WebView {
|
|
* this.
|
|
*/
|
|
public async void clean_content() throws Error {
|
|
- this.call.begin(Util.JS.callable("geary.cleanContent"), null);
|
|
+ this.call_void.begin(Util.JS.callable("cleanContent"), null);
|
|
}
|
|
|
|
/**
|
|
@@ -459,10 +456,10 @@ public class Composer.WebView : Components.WebView {
|
|
const int MAX_BREAKABLE_LEN = 72; // F=F recommended line limit
|
|
const int MAX_UNBREAKABLE_LEN = 998; // SMTP line limit
|
|
|
|
- string body_text = Util.JS.to_string(
|
|
- yield call(Util.JS.callable("geary.getText"), null)
|
|
+ string? body_text = yield call_returning<string?>(
|
|
+ Util.JS.callable("getText"), null
|
|
);
|
|
- string[] lines = body_text.split("\n");
|
|
+ string[] lines = (body_text ?? "").split("\n");
|
|
GLib.StringBuilder flowed = new GLib.StringBuilder.sized(body_text.length);
|
|
foreach (string line in lines) {
|
|
// Strip trailing whitespace, so it doesn't look like a
|
|
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
|
|
index 4c4d0caf..9148a88e 100644
|
|
--- a/src/client/composer/composer-widget.vala
|
|
+++ b/src/client/composer/composer-widget.vala
|
|
@@ -1450,15 +1450,16 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
|
|
confirmation = _("Send message with an empty subject?");
|
|
} else if (!has_body && !has_attachment) {
|
|
confirmation = _("Send message with an empty body?");
|
|
- } else if (!has_attachment &&
|
|
- yield this.editor.body.contains_attachment_keywords(
|
|
- string.join(
|
|
- "|",
|
|
- ATTACHMENT_KEYWORDS,
|
|
- ATTACHMENT_KEYWORDS_LOCALISED
|
|
- ),
|
|
- this.subject)) {
|
|
- confirmation = _("Send message without an attachment?");
|
|
+ } else if (!has_attachment) {
|
|
+ var keywords = string.join(
|
|
+ "|", ATTACHMENT_KEYWORDS, ATTACHMENT_KEYWORDS_LOCALISED
|
|
+ );
|
|
+ var contains = yield this.editor.body.contains_attachment_keywords(
|
|
+ keywords, this.subject
|
|
+ );
|
|
+ if (contains != null && contains) {
|
|
+ confirmation = _("Send message without an attachment?");
|
|
+ }
|
|
}
|
|
if (confirmation != null) {
|
|
ConfirmationDialog dialog = new ConfirmationDialog(container.top_window,
|
|
diff --git a/src/client/conversation-viewer/conversation-web-view.vala b/src/client/conversation-viewer/conversation-web-view.vala
|
|
index ffa36394..d77af642 100644
|
|
--- a/src/client/conversation-viewer/conversation-web-view.vala
|
|
+++ b/src/client/conversation-viewer/conversation-web-view.vala
|
|
@@ -89,20 +89,18 @@ public class ConversationWebView : Components.WebView {
|
|
* Returns the current selection, for prefill as find text.
|
|
*/
|
|
public async string? get_selection_for_find() throws Error{
|
|
- JSC.Value result = yield call(
|
|
- Util.JS.callable("geary.getSelectionForFind"), null
|
|
+ return yield call_returning<string?>(
|
|
+ Util.JS.callable("getSelectionForFind"), null
|
|
);
|
|
- return Util.JS.to_string(result);
|
|
}
|
|
|
|
/**
|
|
* Returns the current selection, for quoting in a message.
|
|
*/
|
|
public async string? get_selection_for_quoting() throws Error {
|
|
- JSC.Value result = yield call(
|
|
- Util.JS.callable("geary.getSelectionForQuoting"), null
|
|
+ return yield call_returning<string?>(
|
|
+ Util.JS.callable("getSelectionForQuoting"), null
|
|
);
|
|
- return Util.JS.to_string(result);
|
|
}
|
|
|
|
/**
|
|
@@ -110,10 +108,9 @@ public class ConversationWebView : Components.WebView {
|
|
*/
|
|
public async int? get_anchor_target_y(string anchor_body)
|
|
throws GLib.Error {
|
|
- JSC.Value result = yield call(
|
|
- Util.JS.callable("geary.getAnchorTargetY").string(anchor_body), null
|
|
+ return yield call_returning<int?>(
|
|
+ Util.JS.callable("getAnchorTargetY").string(anchor_body), null
|
|
);
|
|
- return (int) Util.JS.to_int32(result);
|
|
}
|
|
|
|
/**
|
|
diff --git a/src/client/util/util-js.vala b/src/client/util/util-js.vala
|
|
index 2f05a3e2..d2ce9f2e 100644
|
|
--- a/src/client/util/util-js.vala
|
|
+++ b/src/client/util/util-js.vala
|
|
@@ -348,40 +348,54 @@ namespace Util.JS {
|
|
*/
|
|
public class Callable {
|
|
|
|
- private string base_name;
|
|
- private string[] safe_args = new string[0];
|
|
+ private string name;
|
|
+ private GLib.Variant[] args = {};
|
|
|
|
|
|
- public Callable(string base_name) {
|
|
- this.base_name = base_name;
|
|
+ public Callable(string name) {
|
|
+ this.name = name;
|
|
+ }
|
|
+
|
|
+ public WebKit.UserMessage to_message() {
|
|
+ GLib.Variant? args = null;
|
|
+ if (this.args.length == 1) {
|
|
+ args = this.args[0];
|
|
+ } else if (this.args.length > 1) {
|
|
+ args = new GLib.Variant.tuple(this.args);
|
|
+ }
|
|
+ return new WebKit.UserMessage(this.name, args);
|
|
}
|
|
|
|
public string to_string() {
|
|
- return base_name + "(" + global::string.joinv(",", safe_args) + ");";
|
|
+ string[] args = new string[this.args.length];
|
|
+ for (int i = 0; i < args.length; i++) {
|
|
+ args[i] = this.args[i].print(true);
|
|
+ }
|
|
+ return this.name + "(" + global::string.joinv(",", args) + ")";
|
|
}
|
|
|
|
public Callable string(string value) {
|
|
- add_param("\"" + escape_string(value) + "\"");
|
|
+ add_param(new GLib.Variant.string(value));
|
|
return this;
|
|
}
|
|
|
|
public Callable double(double value) {
|
|
- add_param(value.to_string());
|
|
+ add_param(new GLib.Variant.double(value));
|
|
return this;
|
|
}
|
|
|
|
public Callable int(int value) {
|
|
- add_param(value.to_string());
|
|
+ add_param(new GLib.Variant.int32(value));
|
|
return this;
|
|
}
|
|
|
|
public Callable bool(bool value) {
|
|
- add_param(value ? "true" : "false");
|
|
+ add_param(new GLib.Variant.boolean(value));
|
|
return this;
|
|
}
|
|
|
|
- private inline void add_param(string value) {
|
|
- this.safe_args += value;
|
|
+ private inline void add_param(GLib.Variant value) {
|
|
+ this.args += value;
|
|
}
|
|
|
|
}
|
|
diff --git a/src/client/web-process/web-process-extension.vala b/src/client/web-process/web-process-extension.vala
|
|
index 4bba5154..86f7f44c 100644
|
|
--- a/src/client/web-process/web-process-extension.vala
|
|
+++ b/src/client/web-process/web-process-extension.vala
|
|
@@ -30,6 +30,10 @@ public void webkit_web_extension_initialize_with_user_data(WebKit.WebExtension e
|
|
*/
|
|
public class GearyWebExtension : Object {
|
|
|
|
+ private const string PAGE_STATE_OBJECT_NAME = "geary";
|
|
+ private const string MESSAGE_RETURN_VALUE_NAME = "__return__";
|
|
+ private const string MESSAGE_EXCEPTION_NAME = "__exception__";
|
|
+
|
|
private const string[] ALLOWED_SCHEMES = { "cid", "geary", "data", "blob" };
|
|
|
|
private const string REMOTE_LOAD_VAR = "_gearyAllowRemoteResourceLoads";
|
|
@@ -157,6 +161,55 @@ public class GearyWebExtension : Object {
|
|
page.get_editor().selection_changed.connect(() => {
|
|
selection_changed(page);
|
|
});
|
|
+ page.user_message_received.connect(on_page_message_received);
|
|
+ }
|
|
+
|
|
+ private bool on_page_message_received(WebKit.WebPage page,
|
|
+ WebKit.UserMessage message) {
|
|
+ WebKit.Frame frame = page.get_main_frame();
|
|
+ JSC.Context context = frame.get_js_context();
|
|
+ JSC.Value page_state = context.get_value(PAGE_STATE_OBJECT_NAME);
|
|
+
|
|
+ try {
|
|
+ JSC.Value[]? call_param = null;
|
|
+ GLib.Variant? message_param = message.parameters;
|
|
+ if (message_param != null) {
|
|
+ if (message_param.is_container()) {
|
|
+ size_t len = message_param.n_children();
|
|
+ call_param = new JSC.Value[len];
|
|
+ for (size_t i = 0; i < len; i++) {
|
|
+ call_param[i] = Util.JS.variant_to_value(
|
|
+ context,
|
|
+ message_param.get_child_value(i)
|
|
+ );
|
|
+ }
|
|
+ } else {
|
|
+ call_param = {
|
|
+ Util.JS.variant_to_value(context, message_param)
|
|
+ };
|
|
+ }
|
|
+ }
|
|
+
|
|
+ JSC.Value ret = page_state.object_invoke_methodv(
|
|
+ message.name, call_param
|
|
+ );
|
|
+
|
|
+ // Must send a reply, even for void calls, otherwise
|
|
+ // WebKitGTK will complain. So return a message return
|
|
+ // rain hail or shine.
|
|
+ // https://bugs.webkit.org/show_bug.cgi?id=215880
|
|
+
|
|
+ message.send_reply(
|
|
+ new WebKit.UserMessage(
|
|
+ MESSAGE_RETURN_VALUE_NAME,
|
|
+ Util.JS.value_to_variant(ret)
|
|
+ )
|
|
+ );
|
|
+ } catch (GLib.Error err) {
|
|
+ debug("Failed to handle message: %s", err.message);
|
|
+ }
|
|
+
|
|
+ return true;
|
|
}
|
|
|
|
}
|
|
diff --git a/test/js/components-page-state-test.vala b/test/js/components-page-state-test.vala
|
|
index 5ec75746..562c6cda 100644
|
|
--- a/test/js/components-page-state-test.vala
|
|
+++ b/test/js/components-page-state-test.vala
|
|
@@ -14,12 +14,24 @@ class Components.PageStateTest : WebViewTestCase<WebView> {
|
|
base(config);
|
|
}
|
|
|
|
+ public new async void call_void(Util.JS.Callable callable)
|
|
+ throws GLib.Error {
|
|
+ yield base.call_void(callable, null);
|
|
+ }
|
|
+
|
|
+ public new async string call_returning(Util.JS.Callable callable)
|
|
+ throws GLib.Error {
|
|
+ return yield base.call_returning<string>(callable, null);
|
|
+ }
|
|
+
|
|
}
|
|
|
|
|
|
public PageStateTest() {
|
|
base("Components.PageStateTest");
|
|
add_test("content_loaded", content_loaded);
|
|
+ add_test("call_void", call_void);
|
|
+ add_test("call_returning", call_returning);
|
|
|
|
try {
|
|
WebView.load_resources(GLib.File.new_for_path("/tmp"));
|
|
@@ -45,6 +57,30 @@ class Components.PageStateTest : WebViewTestCase<WebView> {
|
|
assert(content_loaded_triggered);
|
|
}
|
|
|
|
+ public void call_void() throws GLib.Error {
|
|
+ load_body_fixture("OHHAI");
|
|
+ var test_article = this.test_view as TestWebView;
|
|
+
|
|
+ test_article.call_void.begin(
|
|
+ new Util.JS.Callable("testVoid"), this.async_completion
|
|
+ );
|
|
+ test_article.call_void.end(this.async_result());
|
|
+ assert_test_result("void");
|
|
+ }
|
|
+
|
|
+ public void call_returning() throws GLib.Error {
|
|
+ load_body_fixture("OHHAI");
|
|
+ var test_article = this.test_view as TestWebView;
|
|
+
|
|
+ test_article.call_returning.begin(
|
|
+ new Util.JS.Callable("testReturn").string("check 1-2"),
|
|
+ this.async_completion
|
|
+ );
|
|
+ string ret = test_article.call_returning.end(this.async_result());
|
|
+ assert_equal(ret, "check 1-2");
|
|
+ assert_test_result("check 1-2");
|
|
+ }
|
|
+
|
|
protected override WebView set_up_test_view() {
|
|
WebKit.UserScript test_script;
|
|
test_script = new WebKit.UserScript(
|
|
@@ -60,4 +96,13 @@ class Components.PageStateTest : WebViewTestCase<WebView> {
|
|
return view;
|
|
}
|
|
|
|
+ private void assert_test_result(string expected)
|
|
+ throws GLib.Error {
|
|
+ string? result = Util.JS.to_string(
|
|
+ run_javascript("geary.testResult")
|
|
+ .get_js_value()
|
|
+ );
|
|
+ assert_equal(result, expected);
|
|
+ }
|
|
+
|
|
}
|
|
diff --git a/ui/components-web-view.js b/ui/components-web-view.js
|
|
index 80e86d7c..289abca0 100644
|
|
--- a/ui/components-web-view.js
|
|
+++ b/ui/components-web-view.js
|
|
@@ -87,6 +87,8 @@ PageState.prototype = {
|
|
window.addEventListener("transitionend", function(e) {
|
|
queuePreferredHeightUpdate();
|
|
}, false); // load does not bubble
|
|
+
|
|
+ this.testResult = null;
|
|
},
|
|
getPreferredHeight: function() {
|
|
// Return the scroll height of the HTML element since the BODY
|
|
@@ -184,5 +186,13 @@ PageState.prototype = {
|
|
this.hasSelection = hasSelection;
|
|
window.webkit.messageHandlers.selectionChanged.postMessage(hasSelection);
|
|
}
|
|
+ },
|
|
+ // Methods below are for unit tests.
|
|
+ testVoid: function() {
|
|
+ this.testResult = "void";
|
|
+ },
|
|
+ testReturn: function(value) {
|
|
+ this.testResult = value;
|
|
+ return value;
|
|
}
|
|
};
|
|
--
|
|
2.29.2
|
|
|