diff --git a/src/client/components/client-web-view.vala b/src/client/components/client-web-view.vala index d2a77f32..274cb3d4 100644 --- a/src/client/components/client-web-view.vala +++ b/src/client/components/client-web-view.vala @@ -25,6 +25,8 @@ public class ClientWebView : WebKit.WebView { /** URI Scheme and delimiter for images loaded by Content-ID. */ public const string CID_URL_PREFIX = "cid:"; + + private const string CONTENT_LOADED = "contentLoaded"; private const string PREFERRED_HEIGHT_CHANGED = "preferredHeightChanged"; private const string REMOTE_IMAGE_LOAD_BLOCKED = "remoteImageLoadBlocked"; private const string SELECTION_CHANGED = "selectionChanged"; @@ -172,6 +174,22 @@ public class ClientWebView : WebKit.WebView { /** Delegate for UserContentManager message callbacks. */ public delegate void JavaScriptMessageHandler(WebKit.JavascriptResult js_result); + /** + * Determines if the view's content has been fully loaded. + * + * This property is updated immediately before the {@link + * content_loaded} signal is fired, and is triggered by the + * PageState JavaScript object completing its load + * handler. I.e. This will be true after the in-page JavaScript has + * finished making any modifications to the page content. + * + * This will likely be fired after WebKitGTK sets the `is-loading` + * property to `FALSE` and emits `load-changed` with + * `WebKitLoadEvent.LOAD_FINISHED`, since they are related to + * network resource loading, not page content. + */ + public bool is_content_loaded { get; private set; default = false; } + /** Determines if the view has any selected text */ public bool has_selection { get; private set; default = false; } @@ -217,6 +235,14 @@ public class ClientWebView : WebKit.WebView { new Gee.HashMap(); + /** + * Emitted when the view's content has finished loaded. + * + * See {@link is_content_loaded} for detail about when this is + * emitted. + */ + public signal void content_loaded(); + /** Emitted when the view's selection has changed. */ public signal void selection_changed(bool has_selection); @@ -265,6 +291,9 @@ public class ClientWebView : WebKit.WebView { return Gdk.EVENT_PROPAGATE; }); + register_message_handler( + CONTENT_LOADED, on_content_loaded + ); register_message_handler( PREFERRED_HEIGHT_CHANGED, on_preferred_height_changed ); @@ -273,7 +302,7 @@ public class ClientWebView : WebKit.WebView { ); register_message_handler( SELECTION_CHANGED, on_selection_changed - ); + ); // Manage zoom level config.bind(Configuration.CONVERSATION_VIEWER_ZOOM_KEY, this, "zoom_level"); @@ -500,6 +529,11 @@ public class ClientWebView : WebKit.WebView { remote_image_load_blocked(); } + private void on_content_loaded(WebKit.JavascriptResult result) { + this.is_content_loaded = true; + content_loaded(); + } + private void on_selection_changed(WebKit.JavascriptResult result) { try { bool has_selection = WebKitUtil.to_bool(result); @@ -518,4 +552,3 @@ public class ClientWebView : WebKit.WebView { // XXX this needs to be moved into the libsoup bindings extern string soup_uri_decode(string part); - diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 052990ee..e962195e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -27,6 +27,7 @@ set(TEST_SRC client/components/client-web-view-test-case.vala client/composer/composer-web-view-test.vala + js/client-page-state-test.vala js/composer-page-state-test.vala js/conversation-page-state-test.vala ) diff --git a/test/client/components/client-web-view-test-case.vala b/test/client/components/client-web-view-test-case.vala index 68b2b1c5..2a26c22d 100644 --- a/test/client/components/client-web-view-test-case.vala +++ b/test/client/components/client-web-view-test-case.vala @@ -38,7 +38,7 @@ public abstract class ClientWebViewTestCase : Gee.TestCase { protected virtual void load_body_fixture(string html = "") { ClientWebView client_view = (ClientWebView) this.test_view; client_view.load_html(html); - while (client_view.is_loading) { + while (!client_view.is_content_loaded) { Gtk.main_iteration(); } } diff --git a/test/js/client-page-state-test.vala b/test/js/client-page-state-test.vala new file mode 100644 index 00000000..57e02cce --- /dev/null +++ b/test/js/client-page-state-test.vala @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class ClientPageStateTest : ClientWebViewTestCase { + + + public ClientPageStateTest() { + base("ClientPageStateTest"); + add_test("content_loaded", content_loaded); + + try { + ClientWebView.load_scripts(); + } catch (Error err) { + assert_not_reached(); + } + + } + + public void content_loaded() { + bool content_loaded_triggered = false; + this.test_view.content_loaded.connect(() => { + content_loaded_triggered = true; + }); + + assert(!this.test_view.is_content_loaded); + + // XXX sketchy - this call will never return if the thing we + // are testing does not work + load_body_fixture("OHHAI"); + + assert(this.test_view.is_content_loaded); + assert(content_loaded_triggered); + } + + protected override ClientWebView set_up_test_view() { + WebKit.UserScript test_script; + test_script = new WebKit.UserScript( + "var geary = new PageState()", + WebKit.UserContentInjectedFrames.TOP_FRAME, + WebKit.UserScriptInjectionTime.START, + null, + null + ); + + ClientWebView view = new ClientWebView(this.config); + view.get_user_content_manager().add_script(test_script); + return view; + } + +} diff --git a/test/main.vala b/test/main.vala index 8fa3552c..c03db0d7 100644 --- a/test/main.vala +++ b/test/main.vala @@ -64,6 +64,7 @@ int main(string[] args) { TestSuite js = new TestSuite("js"); + js.add_suite(new ClientPageStateTest().get_suite()); js.add_suite(new ComposerPageStateTest().get_suite()); js.add_suite(new ConversationPageStateTest().get_suite()); diff --git a/ui/client-web-view.js b/ui/client-web-view.js index b9aa3d6c..faab9ff0 100644 --- a/ui/client-web-view.js +++ b/ui/client-web-view.js @@ -73,6 +73,7 @@ PageState.prototype = { }, loaded: function() { this.isLoaded = true; + window.webkit.messageHandlers.contentLoaded.postMessage(null); }, loadRemoteImages: function() { this.allowRemoteImages = true;