[geary/wip/714104-refine-account-dialog: 142/180] Push WebView editing machinery from the composer to the client.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/714104-refine-account-dialog: 142/180] Push WebView editing machinery from the composer to the client.
- Date: Mon, 19 Nov 2018 10:16:12 +0000 (UTC)
commit 966d294b60b09ce3b3fb5d107d1dedc5a9520d7d
Author: Michael James Gratton <mike vee net>
Date: Wed Jun 13 17:41:55 2018 +1000
Push WebView editing machinery from the composer to the client.
This allows using the same code for editing account signatures as for the
composer.
* src/client/components/client-web-view.vala (ClientWebView): Move both
command_stack_changed and document_modified signals here from
ComposerWebView, since they use the same underlying machinery to
emit. Move get_html() from ComposerWebView since we want to get to the
HTML when editing signatures. Override WebView.set_editable() so we can
enable/disable the signal machinery and hence not get change signals
emitted when enabling/disabling editing.
* ui/client-web-view.js (PageState): Mirror the changes above made to
ClientWebView.
* src/meson.build (geary_web_process): Ensure C args are passed through
web compiling the web extension.
src/client/components/client-web-view.vala | 45 ++++++++++++++++++++++
src/client/composer/composer-web-view.vala | 62 +++++-------------------------
src/meson.build | 1 +
ui/client-web-view.js | 56 +++++++++++++++++++++++++++
ui/composer-web-view.js | 59 +++++++++-------------------
5 files changed, 131 insertions(+), 92 deletions(-)
---
diff --git a/src/client/components/client-web-view.vala b/src/client/components/client-web-view.vala
index 3b375d98..ad7383f2 100644
--- a/src/client/components/client-web-view.vala
+++ b/src/client/components/client-web-view.vala
@@ -26,7 +26,10 @@ public class ClientWebView : WebKit.WebView, Geary.BaseInterface {
/** URI Scheme and delimiter for images loaded by Content-ID. */
public const string CID_URL_PREFIX = "cid:";
+ // WebKit message handler names
+ private const string COMMAND_STACK_CHANGED = "commandStackChanged";
private const string CONTENT_LOADED = "contentLoaded";
+ private const string DOCUMENT_MODIFIED = "documentModified";
private const string PREFERRED_HEIGHT_CHANGED = "preferredHeightChanged";
private const string REMOTE_IMAGE_LOAD_BLOCKED = "remoteImageLoadBlocked";
private const string SELECTION_CHANGED = "selectionChanged";
@@ -260,6 +263,12 @@ public class ClientWebView : WebKit.WebView, Geary.BaseInterface {
*/
public signal void content_loaded();
+ /** Emitted when the web view's undo/redo stack state changes. */
+ public signal void command_stack_changed(bool can_undo, bool can_redo);
+
+ /** Emitted when the web view's content has changed. */
+ public signal void document_modified();
+
/** Emitted when the view's selection has changed. */
public signal void selection_changed(bool has_selection);
@@ -308,9 +317,15 @@ public class ClientWebView : WebKit.WebView, Geary.BaseInterface {
warning("Web process crashed: %s", reason.to_string());
});
+ register_message_handler(
+ COMMAND_STACK_CHANGED, on_command_stack_changed
+ );
register_message_handler(
CONTENT_LOADED, on_content_loaded
);
+ register_message_handler(
+ DOCUMENT_MODIFIED, on_document_modified
+ );
register_message_handler(
PREFERRED_HEIGHT_CHANGED, on_preferred_height_changed
);
@@ -358,6 +373,15 @@ public class ClientWebView : WebKit.WebView, Geary.BaseInterface {
base.load_html(body, base_uri ?? INTERNAL_URL_BODY);
}
+ /**
+ * Returns the view's content as an HTML string.
+ */
+ public async string? get_html() throws Error {
+ return WebKitUtil.to_string(
+ yield call(Geary.JS.callable("geary.getHtml"), null)
+ );
+ }
+
/**
* Adds an resource that may be accessed from the view via a URL.
*
@@ -441,6 +465,14 @@ public class ClientWebView : WebKit.WebView, Geary.BaseInterface {
notify_property("preferred-height");
}
+ public new async void set_editable(bool enabled,
+ Cancellable? cancellable)
+ throws Error {
+ yield call(
+ Geary.JS.callable("geary.setEditable").bool(enabled), cancellable
+ );
+ }
+
/**
* Invokes a {@link Geary.JS.Callable} on this web view.
*/
@@ -582,6 +614,19 @@ public class ClientWebView : WebKit.WebView, Geary.BaseInterface {
}
}
+ private void on_command_stack_changed(WebKit.JavascriptResult result) {
+ try {
+ string[] values = WebKitUtil.to_string(result).split(",");
+ command_stack_changed(values[0] == "true", values[1] == "true");
+ } catch (Geary.JS.Error err) {
+ debug("Could not get command stack state: %s", err.message);
+ }
+ }
+
+ private void on_document_modified(WebKit.JavascriptResult result) {
+ document_modified();
+ }
+
private void on_remote_image_load_blocked(WebKit.JavascriptResult result) {
remote_image_load_blocked();
}
diff --git a/src/client/composer/composer-web-view.vala b/src/client/composer/composer-web-view.vala
index 59baf4bd..efe83986 100644
--- a/src/client/composer/composer-web-view.vala
+++ b/src/client/composer/composer-web-view.vala
@@ -12,9 +12,9 @@
public class ComposerWebView : ClientWebView {
- private const string COMMAND_STACK_CHANGED = "commandStackChanged";
+ // WebKit message handler names
private const string CURSOR_CONTEXT_CHANGED = "cursorContextChanged";
- private const string DOCUMENT_MODIFIED = "documentModified";
+
/**
* Encapsulates editing-related state for a specific DOM node.
@@ -104,17 +104,6 @@ public class ComposerWebView : ClientWebView {
/** Determines if the view is in rich text mode. */
public bool is_rich_text { get; private set; default = true; }
- // Determines if signals should be sent, useful for e.g. stopping
- // document_modified being sent when the editor content is being
- // updated before sending.
- private bool signals_enabled = true;
-
-
- /** Emitted when the web view's content has changed. */
- public signal void document_modified();
-
- /** Emitted when the web view's undo/redo stack state changes. */
- public signal void command_stack_changed(bool can_undo, bool can_redo);
/** Emitted when the cursor's edit context has changed. */
public signal void cursor_context_changed(EditContext cursor_context);
@@ -131,9 +120,13 @@ public class ComposerWebView : ClientWebView {
this.user_content_manager.add_style_sheet(ComposerWebView.app_style);
this.user_content_manager.add_script(ComposerWebView.app_script);
- register_message_handler(COMMAND_STACK_CHANGED, on_command_stack_changed);
register_message_handler(CURSOR_CONTEXT_CHANGED, on_cursor_context_changed);
- register_message_handler(DOCUMENT_MODIFIED, on_document_modified);
+
+ // XXX this is a bit of a hack given the docs for is_empty,
+ // above
+ this.command_stack_changed.connect((can_undo, can_redo) => {
+ this.is_empty = !can_redo;
+ });
}
/**
@@ -197,7 +190,6 @@ public class ComposerWebView : ClientWebView {
*/
public void disable() {
set_sensitive(false);
- this.signals_enabled = false;
}
/**
@@ -429,15 +421,6 @@ public class ComposerWebView : ClientWebView {
this.call.begin(Geary.JS.callable("geary.cleanContent"), null);
}
- /**
- * Returns the editor content as an HTML string.
- */
- public async string? get_html() throws Error {
- return WebKitUtil.to_string(
- yield call(Geary.JS.callable("geary.getHtml"), null)
- );
- }
-
/**
* Returns the editor text as RFC 3676 format=flowed text.
*/
@@ -515,41 +498,16 @@ public class ComposerWebView : ClientWebView {
// to show a link popopver after the view has processed one,
// we need to emit our own.
bool ret = base.button_release_event(event);
- if (this.signals_enabled) {
- button_release_event_done(event);
- }
+ button_release_event_done(event);
return ret;
}
- private void on_command_stack_changed(WebKit.JavascriptResult result) {
- try {
- if (this.signals_enabled) {
- string[] values = WebKitUtil.to_string(result).split(",");
- command_stack_changed(values[0] == "true", values[1] == "true");
- }
- } catch (Geary.JS.Error err) {
- debug("Could not get command stack state: %s", err.message);
- }
- }
-
private void on_cursor_context_changed(WebKit.JavascriptResult result) {
try {
- if (this.signals_enabled) {
- cursor_context_changed(new EditContext(WebKitUtil.to_string(result)));
- }
+ cursor_context_changed(new EditContext(WebKitUtil.to_string(result)));
} catch (Geary.JS.Error err) {
debug("Could not get text cursor style: %s", err.message);
}
}
- private void on_document_modified(WebKit.JavascriptResult result) {
- // Only modify actually changed to avoid excessive notify
- // signals being fired.
- if (this.signals_enabled) {
- if (this.is_empty) {
- this.is_empty = false;
- }
- document_modified();
- }
- }
}
diff --git a/src/meson.build b/src/meson.build
index 40abe05b..2eef6927 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -51,6 +51,7 @@ subdir('mailer')
# Web process extension library
geary_web_process = library('geary-web-process',
join_paths('client', 'web-process', 'web-process-extension.vala'),
+ c_args: geary_c_options,
dependencies: [
gee,
gmime,
diff --git a/ui/client-web-view.js b/ui/client-web-view.js
index a4542133..c2a9d24a 100644
--- a/ui/client-web-view.js
+++ b/ui/client-web-view.js
@@ -16,11 +16,26 @@ PageState.prototype = {
init: function() {
this.allowRemoteImages = false;
this.isLoaded = false;
+ this.undoEnabled = false;
+ this.redoEnabled = false;
this.hasSelection = false;
this.lastPreferredHeight = 0;
let state = this;
+ // Set up an observer to keep track of modifications made to
+ // the document when editing.
+ let modifiedId = null;
+ this.bodyObserver = new MutationObserver(function(records) {
+ if (modifiedId == null) {
+ modifiedId = window.setTimeout(function() {
+ state.documentModified();
+ state.checkCommandStack();
+ modifiedId = null;
+ }, 1000);
+ }
+ });
+
// Coalesce multiple calls to updatePreferredHeight using a
// timeout to avoid the overhead of multiple JS messages sent
// to the app and hence view multiple resizes being queued.
@@ -73,6 +88,9 @@ PageState.prototype = {
getPreferredHeight: function() {
return window.document.documentElement.scrollHeight;
},
+ getHtml: function() {
+ return document.body.innerHTML;
+ },
loaded: function() {
this.isLoaded = true;
window.webkit.messageHandlers.contentLoaded.postMessage(null);
@@ -87,6 +105,29 @@ PageState.prototype = {
img.src = src;
}
},
+ setEditable: function(enabled) {
+ if (!enabled) {
+ this.stopBodyObserver();
+ }
+ document.body.contentEditable = enabled;
+ if (enabled) {
+ // Enable modification observation only after the document
+ // has been set editable as WebKit will alter some attrs
+ this.startBodyObserver();
+ }
+ },
+ startBodyObserver: function() {
+ let config = {
+ attributes: true,
+ childList: true,
+ characterData: true,
+ subtree: true
+ };
+ this.bodyObserver.observe(document.body, config);
+ },
+ stopBodyObserver: function() {
+ this.bodyObserver.disconnect();
+ },
remoteImageLoadBlocked: function() {
window.webkit.messageHandlers.remoteImageLoadBlocked.postMessage(null);
},
@@ -108,6 +149,21 @@ PageState.prototype = {
}
return updated;
},
+ checkCommandStack: function() {
+ let canUndo = document.queryCommandEnabled("undo");
+ let canRedo = document.queryCommandEnabled("redo");
+
+ if (canUndo != this.undoEnabled || canRedo != this.redoEnabled) {
+ this.undoEnabled = canUndo;
+ this.redoEnabled = canRedo;
+ window.webkit.messageHandlers.commandStackChanged.postMessage(
+ this.undoEnabled + "," + this.redoEnabled
+ );
+ }
+ },
+ documentModified: function(element) {
+ window.webkit.messageHandlers.documentModified.postMessage(null);
+ },
selectionChanged: function() {
let hasSelection = !window.getSelection().isCollapsed;
if (this.hasSelection != hasSelection) {
diff --git a/ui/composer-web-view.js b/ui/composer-web-view.js
index 678ce929..3e2439d6 100644
--- a/ui/composer-web-view.js
+++ b/ui/composer-web-view.js
@@ -30,8 +30,6 @@ ComposerPageState.prototype = {
this.quotePart = null;
this.focusedPart = null;
- this.undoEnabled = false;
- this.redoEnabled = false;
this.selections = new Map();
this.nextSelectionId = 0;
this.cursorContext = null;
@@ -43,17 +41,6 @@ ComposerPageState.prototype = {
e.preventDefault();
}
}, true);
-
- let modifiedId = null;
- this.bodyObserver = new MutationObserver(function() {
- if (modifiedId == null) {
- modifiedId = window.setTimeout(function() {
- state.documentModified();
- state.checkCommandStack();
- modifiedId = null;
- }, 1000);
- }
- });
},
loaded: function() {
let state = this;
@@ -138,8 +125,16 @@ ComposerPageState.prototype = {
cursor.parentNode.removeChild(cursor);
}
- // Enable editing and observation machinery only after
- // modifying the body above.
+
+ // Enable editing only after modifying the body above.
+ this.setEditable(true);
+
+ PageState.prototype.loaded.apply(this, []);
+ },
+ setEditable: function(enabled) {
+ if (!enabled) {
+ this.stopBodyObserver();
+ }
this.bodyPart.contentEditable = true;
if (this.signaturePart != null) {
this.signaturePart.contentEditable = true;
@@ -147,16 +142,11 @@ ComposerPageState.prototype = {
if (this.quotePart != null) {
this.quotePart.contentEditable = true;
}
- let config = {
- attributes: true,
- childList: true,
- characterData: true,
- subtree: true
- };
- this.bodyObserver.observe(document.body, config);
-
- // Chain up
- PageState.prototype.loaded.apply(this, []);
+ if (enabled) {
+ // Enable modification observation only after the document
+ // has been set editable as WebKit will alter some attrs
+ this.startBodyObserver();
+ }
},
undo: function() {
document.execCommand("undo", false, null);
@@ -305,6 +295,10 @@ ComposerPageState.prototype = {
}
},
cleanContent: function() {
+ // Prevent any modification signals being sent when mutating
+ // the document below.
+ this.stopBodyObserver();
+
ComposerPageState.cleanPart(this.bodyPart, false);
ComposerPageState.linkify(this.bodyPart);
@@ -345,21 +339,6 @@ ComposerPageState.prototype = {
document.body.classList.add("plain");
}
},
- checkCommandStack: function() {
- let canUndo = document.queryCommandEnabled("undo");
- let canRedo = document.queryCommandEnabled("redo");
-
- if (canUndo != this.undoEnabled || canRedo != this.redoEnabled) {
- this.undoEnabled = canUndo;
- this.redoEnabled = canRedo;
- window.webkit.messageHandlers.commandStackChanged.postMessage(
- this.undoEnabled + "," + this.redoEnabled
- );
- }
- },
- documentModified: function(element) {
- window.webkit.messageHandlers.documentModified.postMessage(null);
- },
selectionChanged: function() {
PageState.prototype.selectionChanged.apply(this, []);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]