Apply custom user CSS to composer web view as well as to conversations

This allows people with dark themes to apply style to the cmoposer's
body editor as well as to conversation bodies. Note that this CSS does
not get sent, so WYSIWYG will break if people choose to do this.

Also renames the user CSS file from user-message.css to user-style.css,
but still looks for the old name for now.

Fixes https://bugzilla.gnome.org/show_bug.cgi?id=714129
This commit is contained in:
Michael Gratton 2019-02-28 18:34:28 +11:00
parent 092a5a3cca
commit a6c1962e47
8 changed files with 54 additions and 43 deletions

View file

@ -250,11 +250,11 @@ public class GearyController : Geary.BaseObject {
Args.log_debug
);
try {
ClientWebView.load_scripts();
ComposerWebView.load_resources();
ConversationWebView.load_resources(
ClientWebView.load_resources(
this.application.get_user_config_directory()
);
ComposerWebView.load_resources();
ConversationWebView.load_resources();
} catch (Error err) {
error("Error loading web resources: %s", err.message);
}

View file

@ -39,6 +39,10 @@ public class ClientWebView : WebKit.WebView, Geary.BaseInterface {
private const double ZOOM_MAX = 2.0;
private const double ZOOM_MIN = 0.5;
private const string USER_CSS = "user-style.css";
private const string USER_CSS_LEGACY = "user-message.css";
// Workaround WK binding ctor not accepting any args
private class WebsiteDataManager : WebKit.WebsiteDataManager {
@ -58,9 +62,12 @@ public class ClientWebView : WebKit.WebView, Geary.BaseInterface {
private static WebKit.WebContext? default_context = null;
private static WebKit.UserStyleSheet? user_stylesheet = null;
private static WebKit.UserScript? script = null;
private static WebKit.UserScript? allow_remote_images = null;
/**
* Initialises WebKit.WebContext for use by the client.
*/
@ -109,19 +116,35 @@ public class ClientWebView : WebKit.WebView, Geary.BaseInterface {
/**
* Loads static resources used by ClientWebView.
*/
public static void load_scripts()
throws Error {
public static void load_resources(GLib.File user_dir)
throws GLib.Error {
ClientWebView.script = load_app_script(
"client-web-view.js"
);
ClientWebView.allow_remote_images = load_app_script(
"client-web-view-allow-remote-images.js"
);
foreach (string name in new string[] { USER_CSS, USER_CSS_LEGACY }) {
GLib.File stylesheet = user_dir.get_child(name);
try {
ClientWebView.user_stylesheet = load_user_stylesheet(stylesheet);
break;
} catch (GLib.IOError.NOT_FOUND err) {
// All good, try the next one or just exit
} catch (GLib.FileError.NOENT err) {
// Ditto
} catch (GLib.Error err) {
warning(
"Could not load %s: %s", stylesheet.get_path(), err.message
);
}
}
}
/** Loads an application-specific WebKit stylesheet. */
protected static WebKit.UserStyleSheet load_app_stylesheet(string name)
throws Error {
throws GLib.Error {
return new WebKit.UserStyleSheet(
GioUtil.read_resource(name),
WebKit.UserContentInjectedFrames.TOP_FRAME,
@ -131,24 +154,17 @@ public class ClientWebView : WebKit.WebView, Geary.BaseInterface {
);
}
/** Loads a user stylesheet, if any. */
protected static WebKit.UserStyleSheet? load_user_stylesheet(File name) {
WebKit.UserStyleSheet? user_stylesheet = null;
try {
Geary.Memory.FileBuffer buf = new Geary.Memory.FileBuffer(name, true);
user_stylesheet = new WebKit.UserStyleSheet(
buf.get_valid_utf8(),
WebKit.UserContentInjectedFrames.ALL_FRAMES,
WebKit.UserStyleLevel.USER,
null,
null
);
} catch (IOError.NOT_FOUND err) {
debug("User CSS file does not exist: %s", err.message);
} catch (Error err) {
debug("Failed to load user CSS file: %s", err.message);
}
return user_stylesheet;
/** Loads a user stylesheet from disk. */
protected static WebKit.UserStyleSheet? load_user_stylesheet(GLib.File name)
throws GLib.Error {
Geary.Memory.FileBuffer buf = new Geary.Memory.FileBuffer(name, true);
return new WebKit.UserStyleSheet(
buf.get_valid_utf8(),
WebKit.UserContentInjectedFrames.ALL_FRAMES,
WebKit.UserStyleLevel.USER,
null,
null
);
}
/** Loads an application-specific WebKit JavaScript script. */
@ -299,6 +315,9 @@ public class ClientWebView : WebKit.WebView, Geary.BaseInterface {
WebKit.UserContentManager content_manager =
custom_manager ?? new WebKit.UserContentManager();
content_manager.add_script(ClientWebView.script);
if (ClientWebView.user_stylesheet != null) {
content_manager.add_style_sheet(ClientWebView.user_stylesheet);
}
Object(
web_context: ClientWebView.default_context,

View file

@ -81,7 +81,7 @@ public class ComposerWebView : ClientWebView {
private static WebKit.UserStyleSheet? app_style = null;
private static WebKit.UserScript? app_script = null;
public static void load_resources()
public static new void load_resources()
throws Error {
ComposerWebView.app_style = ClientWebView.load_app_stylesheet(
"composer-web-view.css"

View file

@ -8,7 +8,6 @@
public class ConversationWebView : ClientWebView {
private const string USER_CSS = "user-message.css";
private const string DECEPTIVE_LINK_CLICKED = "deceptiveLinkClicked";
@ -37,11 +36,10 @@ public class ConversationWebView : ClientWebView {
DECEPTIVE_DOMAIN = 2;
}
private static WebKit.UserStyleSheet? user_stylesheet = null;
private static WebKit.UserStyleSheet? app_stylesheet = null;
private static WebKit.UserScript? app_script = null;
public static void load_resources(File user_dir)
public static new void load_resources()
throws Error {
ConversationWebView.app_script = ClientWebView.load_app_script(
"conversation-web-view.js"
@ -49,9 +47,6 @@ public class ConversationWebView : ClientWebView {
ConversationWebView.app_stylesheet = ClientWebView.load_app_stylesheet(
"conversation-web-view.css"
);
ConversationWebView.user_stylesheet = ClientWebView.load_user_stylesheet(
user_dir.get_child("user-message.css")
);
}
@ -65,9 +60,6 @@ public class ConversationWebView : ClientWebView {
base(config);
this.user_content_manager.add_script(ConversationWebView.app_script);
this.user_content_manager.add_style_sheet(ConversationWebView.app_stylesheet);
if (ConversationWebView.user_stylesheet != null) {
this.user_content_manager.add_style_sheet(ConversationWebView.user_stylesheet);
}
register_message_handler(
DECEPTIVE_LINK_CLICKED, on_deceptive_link_clicked

View file

@ -23,8 +23,8 @@ public abstract class ClientWebViewTestCase<V> : TestCase {
true
);
try {
ClientWebView.load_scripts();
} catch (Error err) {
ClientWebView.load_resources(GLib.File.new_for_path("/tmp"));
} catch (GLib.Error err) {
assert_not_reached();
}
}

View file

@ -23,10 +23,10 @@ public class ClientWebViewTest : TestCase {
);
}
public void load_resources() throws Error {
public void load_resources() throws GLib.Error {
try {
ClientWebView.load_scripts();
} catch (Error err) {
ClientWebView.load_resources(GLib.File.new_for_path("/tmp"));
} catch (GLib.Error err) {
assert_not_reached();
}
}

View file

@ -13,8 +13,8 @@ class ClientPageStateTest : ClientWebViewTestCase<ClientWebView> {
add_test("content_loaded", content_loaded);
try {
ClientWebView.load_scripts();
} catch (Error err) {
ClientWebView.load_resources(GLib.File.new_for_path("/tmp"));
} catch (GLib.Error err) {
assert_not_reached();
}

View file

@ -23,8 +23,8 @@ class ConversationPageStateTest : ClientWebViewTestCase<ConversationWebView> {
add_test("is_descendant_of_lax", is_descendant_of_lax);
try {
ConversationWebView.load_resources(File.new_for_path(""));
} catch (Error err) {
ConversationWebView.load_resources();
} catch (GLib.Error err) {
assert_not_reached();
}
}