From c813aa5707acc5226a57dca82449dc709969d05a Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Thu, 27 Aug 2020 16:18:45 +1000 Subject: [PATCH 071/124] Components.WebView: Check for pass up exceptions when calling JS code Update web extension to check for errors when invoking page state methods and pass a message back if found. Check for this, decode and throw a vala error in the WebView if found. --- .../components/components-web-view.vala | 56 ++++++++++++++--- .../web-process/web-process-extension.vala | 39 ++++++++++-- test/js/components-page-state-test.vala | 60 +++++++++++++++++++ ui/components-web-view.js | 4 ++ 4 files changed, 146 insertions(+), 13 deletions(-) diff --git a/src/client/components/components-web-view.vala b/src/client/components/components-web-view.vala index 368b6a8d..2b373170 100644 --- a/src/client/components/components-web-view.vala +++ b/src/client/components/components-web-view.vala @@ -26,6 +26,10 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { /** URI Scheme and delimiter for images loaded by Content-ID. */ public const string CID_URL_PREFIX = "cid:"; + // Keep these in sync with GearyWebExtension + private const string MESSAGE_RETURN_VALUE_NAME = "__return__"; + private const string MESSAGE_EXCEPTION_NAME = "__exception__"; + // WebKit message handler names private const string COMMAND_STACK_CHANGED = "commandStackChanged"; private const string CONTENT_LOADED = "contentLoaded"; @@ -467,9 +471,7 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { protected async void call_void(Util.JS.Callable target, GLib.Cancellable? cancellable) throws GLib.Error { - yield send_message_to_page( - target.to_message(), cancellable - ); + yield call_impl(target, cancellable); } /** @@ -488,12 +490,10 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { protected async T call_returning(Util.JS.Callable target, GLib.Cancellable? cancellable) throws GLib.Error { - WebKit.UserMessage? response = yield send_message_to_page( - target.to_message(), cancellable - ); + WebKit.UserMessage? response = yield call_impl(target, cancellable); if (response == null) { throw new Util.JS.Error.TYPE( - "Method call did not return a value: %s", target.to_string() + "Method call %s did not return a value", target.to_string() ); } GLib.Variant? param = response.parameters; @@ -612,6 +612,48 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface { "monospace-font", SettingsBindFlags.DEFAULT); } + private async WebKit.UserMessage? call_impl(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) { + var response_name = response.name; + if (response_name == MESSAGE_EXCEPTION_NAME) { + var exception = new GLib.VariantDict(response.parameters); + var name = exception.lookup_value("name", GLib.VariantType.STRING) as string; + var message = exception.lookup_value("message", GLib.VariantType.STRING) as string; + var backtrace = exception.lookup_value("backtrace_string", GLib.VariantType.STRING) as string; + var source = exception.lookup_value("source_uri", GLib.VariantType.STRING) as string; + var line = exception.lookup_value("line_number", GLib.VariantType.UINT32); + var column = exception.lookup_value("column_number", GLib.VariantType.UINT32); + + var log_message = "Method call %s raised %s exception at %s:%d:%d: %s".printf( + target.to_string(), + name ?? "unknown", + source ?? "unknown", + (line != null ? (int) line.get_uint32() : -1), + (column != null ? (int) column.get_uint32() : -1), + message ?? "unknown" + ); + debug(log_message); + if (backtrace != null) { + debug(backtrace); + } + + throw new Util.JS.Error.EXCEPTION(log_message); + } else if (response_name != MESSAGE_RETURN_VALUE_NAME) { + throw new Util.JS.Error.TYPE( + "Method call %s returned unknown name: %s", + target.to_string(), + response_name + ); + } + } + return response; + } + private void handle_cid_request(WebKit.URISchemeRequest request) { if (!handle_internal_response(request)) { request.finish_error(new FileError.NOENT("Unknown CID")); diff --git a/src/client/web-process/web-process-extension.vala b/src/client/web-process/web-process-extension.vala index 86f7f44c..7aa6dd3c 100644 --- a/src/client/web-process/web-process-extension.vala +++ b/src/client/web-process/web-process-extension.vala @@ -31,6 +31,8 @@ public void webkit_web_extension_initialize_with_user_data(WebKit.WebExtension e public class GearyWebExtension : Object { private const string PAGE_STATE_OBJECT_NAME = "geary"; + + // Keep these in sync with Components.WebView private const string MESSAGE_RETURN_VALUE_NAME = "__return__"; private const string MESSAGE_EXCEPTION_NAME = "__exception__"; @@ -199,12 +201,37 @@ public class GearyWebExtension : Object { // 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) - ) - ); + JSC.Exception? thrown = context.get_exception(); + if (thrown != null) { + var detail = new GLib.VariantDict(); + if (thrown.get_message() != null) { + detail.insert_value("name", new GLib.Variant.string(thrown.get_name())); + } + if (thrown.get_message() != null) { + detail.insert_value("message", new GLib.Variant.string(thrown.get_message())); + } + if (thrown.get_backtrace_string() != null) { + detail.insert_value("backtrace_string", new GLib.Variant.string(thrown.get_backtrace_string())); + } + if (thrown.get_source_uri() != null) { + detail.insert_value("source_uri", new GLib.Variant.string(thrown.get_source_uri())); + } + detail.insert_value("line_number", new GLib.Variant.uint32(thrown.get_line_number())); + detail.insert_value("column_number", new GLib.Variant.uint32(thrown.get_column_number())); + message.send_reply( + new WebKit.UserMessage( + MESSAGE_EXCEPTION_NAME, + detail.end() + ) + ); + } else { + 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); } diff --git a/test/js/components-page-state-test.vala b/test/js/components-page-state-test.vala index 562c6cda..bf952416 100644 --- a/test/js/components-page-state-test.vala +++ b/test/js/components-page-state-test.vala @@ -31,7 +31,9 @@ class Components.PageStateTest : WebViewTestCase { base("Components.PageStateTest"); add_test("content_loaded", content_loaded); add_test("call_void", call_void); + add_test("call_void_throws", call_void_throws); add_test("call_returning", call_returning); + add_test("call_returning_throws", call_returning_throws); try { WebView.load_resources(GLib.File.new_for_path("/tmp")); @@ -68,6 +70,35 @@ class Components.PageStateTest : WebViewTestCase { assert_test_result("void"); } + public void call_void_throws() throws GLib.Error { + load_body_fixture("OHHAI"); + var test_article = this.test_view as TestWebView; + + try { + test_article.call_void.begin( + new Util.JS.Callable("testThrow").string("void message"), + this.async_completion + ); + test_article.call_void.end(this.async_result()); + assert_not_reached(); + } catch (Util.JS.Error.EXCEPTION err) { + assert_string( + err.message + ).contains( + "testThrow" + // WebKitGTK doesn't actually pass any details through: + // https://bugs.webkit.org/show_bug.cgi?id=215877 + // ).contains( + // "Error" + // ).contains( + // "void message" + // ).contains( + // "components-web-view.js" + ); + assert_test_result("void message"); + } + } + public void call_returning() throws GLib.Error { load_body_fixture("OHHAI"); var test_article = this.test_view as TestWebView; @@ -81,6 +112,35 @@ class Components.PageStateTest : WebViewTestCase { assert_test_result("check 1-2"); } + public void call_returning_throws() throws GLib.Error { + load_body_fixture("OHHAI"); + var test_article = this.test_view as TestWebView; + + try { + test_article.call_returning.begin( + new Util.JS.Callable("testThrow").string("return message"), + this.async_completion + ); + test_article.call_returning.end(this.async_result()); + assert_not_reached(); + } catch (Util.JS.Error.EXCEPTION err) { + assert_string( + err.message + ).contains( + "testThrow" + // WebKitGTK doesn't actually pass any details through: + // https://bugs.webkit.org/show_bug.cgi?id=215877 + // ).contains( + // "Error" + // ).contains( + // "return message" + // ).contains( + // "components-web-view.js" + ); + assert_test_result("return message"); + } + } + protected override WebView set_up_test_view() { WebKit.UserScript test_script; test_script = new WebKit.UserScript( diff --git a/ui/components-web-view.js b/ui/components-web-view.js index 289abca0..0f932a19 100644 --- a/ui/components-web-view.js +++ b/ui/components-web-view.js @@ -194,5 +194,9 @@ PageState.prototype = { testReturn: function(value) { this.testResult = value; return value; + }, + testThrow: function(value) { + this.testResult = value; + throw this.testResult; } }; -- 2.29.2