diff --git a/src/client/geary-controller.vala b/src/client/geary-controller.vala
index c1d15342..5e373a22 100644
--- a/src/client/geary-controller.vala
+++ b/src/client/geary-controller.vala
@@ -625,7 +625,6 @@ public class GearyController {
if (clear_view) {
main_window.conversation_viewer.clear(current_folder, current_account.information);
main_window.conversation_viewer.scroll_reset();
- main_window.conversation_viewer.external_images_info_bar.hide();
}
// Fetch full messages.
diff --git a/src/client/views/conversation-viewer.vala b/src/client/views/conversation-viewer.vala
index f3915103..a8d74792 100644
--- a/src/client/views/conversation-viewer.vala
+++ b/src/client/views/conversation-viewer.vala
@@ -47,9 +47,6 @@ public class ConversationViewer : Gtk.Box {
// The HTML viewer to view the emails.
public ConversationWebView web_view { get; private set; }
- // The Info Bar to be shown when an external image is blocked.
- public Gtk.InfoBar external_images_info_bar { get; private set; }
-
// Label for displaying overlay messages.
private Gtk.Label message_overlay_label;
@@ -68,21 +65,6 @@ public class ConversationViewer : Gtk.Box {
public ConversationViewer() {
Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
- external_images_info_bar = new Gtk.InfoBar.with_buttons(
- _("_Show Images"), Gtk.ResponseType.OK, _("_Cancel"), Gtk.ResponseType.CANCEL);
- external_images_info_bar.no_show_all = true;
- external_images_info_bar.response.connect(on_external_images_info_bar_response);
- external_images_info_bar.message_type = Gtk.MessageType.WARNING;
- Gtk.Box? external_images_info_bar_content_area =
- external_images_info_bar.get_content_area() as Gtk.Box;
- if (external_images_info_bar_content_area != null) {
- Gtk.Label label = new Gtk.Label(_("This message contains images. Do you want to show them?"));
- label.set_line_wrap(true);
- external_images_info_bar_content_area.add(label);
- label.show_all();
- }
- pack_start(external_images_info_bar, false, false);
-
web_view = new ConversationWebView();
web_view.hovering_over_link.connect(on_hovering_over_link);
@@ -90,7 +72,6 @@ public class ConversationViewer : Gtk.Box {
web_view.realize.connect( () => { web_view.get_vadjustment().value_changed.connect(mark_read); });
web_view.size_allocate.connect(mark_read);
- web_view.image_load_requested.connect(on_image_load_requested);
web_view.link_selected.connect((link) => { link_selected(link); });
Gtk.ScrolledWindow conversation_viewer_scrolled = new Gtk.ScrolledWindow(null, null);
@@ -109,15 +90,6 @@ public class ConversationViewer : Gtk.Box {
pack_start(message_overlay);
}
- private void on_image_load_requested() {
- external_images_info_bar.show();
- }
-
- private void on_external_images_info_bar_response(Gtk.InfoBar sender, int response_id) {
- web_view.apply_load_external_images(response_id == Gtk.ResponseType.OK);
- sender.hide();
- }
-
public Geary.Email? get_last_message() {
return messages.is_empty ? null : messages.last();
}
@@ -166,8 +138,6 @@ public class ConversationViewer : Gtk.Box {
}
public void add_message(Geary.Email email) {
- web_view.apply_load_external_images(false);
-
// Make sure the message container is showing and the multi-message counter hidden.
try {
web_view.show_element_by_id(MESSAGE_CONTAINER_ID);
@@ -277,9 +247,10 @@ public class ConversationViewer : Gtk.Box {
}
string body_text = "";
+ bool remote_images = false;
try {
body_text = email.get_message().get_body(true);
- body_text = insert_html_markup(body_text, email);
+ body_text = insert_html_markup(body_text, email, out remote_images);
} catch (Error err) {
debug("Could not get message text. %s", err.message);
}
@@ -292,6 +263,16 @@ public class ConversationViewer : Gtk.Box {
WebKit.DOM.HTMLElement span_body = Util.DOM.select(div_email_container, ".body");
span_body.set_inner_html(body_text);
+
+ if (remote_images) {
+ WebKit.DOM.HTMLElement remote_images_bar =
+ Util.DOM.select(div_email_container, ".remote_images");
+ ((WebKit.DOM.Element) remote_images_bar).get_class_list().add("show");
+ remote_images_bar.set_inner_html("""%s %s
+ """.printf(
+ remote_images_bar.get_inner_html(), _("This message contains remote images."),
+ _("Show Images")));
+ }
} catch (Error html_error) {
warning("Error setting HTML for message: %s", html_error.message);
@@ -328,6 +309,8 @@ public class ConversationViewer : Gtk.Box {
bind_event(web_view, ".email .compressed_note", "click", (Callback) on_body_toggle_clicked, this);
bind_event(web_view, ".attachment_container .attachment", "click", (Callback) on_attachment_clicked, this);
bind_event(web_view, ".attachment_container .attachment", "contextmenu", (Callback) on_attachment_menu, this);
+ bind_event(web_view, ".remote_images .show_images", "click", (Callback) on_show_images, this);
+ bind_event(web_view, ".remote_images .close_show_images", "click", (Callback) on_close_show_images, this);
}
public void unhide_last_email() {
@@ -645,6 +628,47 @@ public class ConversationViewer : Gtk.Box {
mark_read();
}
+ private static void on_show_images(WebKit.DOM.Element element, WebKit.DOM.Event event,
+ ConversationViewer conversation_viewer) {
+ WebKit.DOM.HTMLElement? email_element = closest_ancestor(element, ".email");
+ if (email_element != null)
+ conversation_viewer.show_images_email(email_element);
+ }
+
+ private void show_images_email(WebKit.DOM.Element email_element) {
+ // TODO: Remember that these images have been shown.
+ try {
+ WebKit.DOM.NodeList nodes = email_element.query_selector_all("img");
+ for (ulong i = 0; i < nodes.length; i++) {
+ WebKit.DOM.Element? element = nodes.item(i) as WebKit.DOM.Element;
+ if (element == null || !element.has_attribute("src"))
+ continue;
+
+ string src = element.get_attribute("src");
+ if (src.has_prefix("remote:"))
+ element.set_attribute("src", src.substring(7));
+ }
+
+ WebKit.DOM.Element? remote_images = email_element.query_selector(".remote_images");
+ if (remote_images != null)
+ remote_images.get_class_list().remove("show");
+ } catch (Error error) {
+ warning("Error showing images: %s", error.message);
+ }
+ }
+
+ private static void on_close_show_images(WebKit.DOM.Element element, WebKit.DOM.Event event,
+ ConversationViewer conversation_viewer) {
+ WebKit.DOM.HTMLElement? remote_images = closest_ancestor(element, ".remote_images");
+ if (remote_images != null) {
+ try {
+ remote_images.get_class_list().remove("show");
+ } catch (Error error) {
+ warning("Error hiding \"Show images\" bar: %s", error.message);
+ }
+ }
+ }
+
private static void on_attachment_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
ConversationViewer conversation_viewer) {
conversation_viewer.on_attachment_clicked_self(element);
@@ -859,7 +883,8 @@ public class ConversationViewer : Gtk.Box {
}
}
- private string insert_html_markup(string text, Geary.Email email) {
+ private string insert_html_markup(string text, Geary.Email email, out bool remote_images) {
+ remote_images = false;
try {
// Create a workspace for manipulating the HTML.
WebKit.DOM.HTMLElement container = web_view.create_div();
@@ -902,9 +927,8 @@ public class ConversationViewer : Gtk.Box {
wrap_html_signature(ref container);
// Then look for all
tags. Inline images are replaced with
- // data URLs, while external images are added to
- // external_images_uri (to be used later by is_image()).
- Gee.ArrayList external_images_uri = new Gee.ArrayList();
+ // data URLs, while external images have the prefix "remote:" added
+ // to their src, which is trapped in the conversation_web_view.
WebKit.DOM.NodeList inline_list = container.query_selector_all("img");
for (ulong i = 0; i < inline_list.length; ++i) {
// Get the MIME content for the image.
@@ -925,15 +949,12 @@ public class ConversationViewer : Gtk.Box {
// Then set the source to a data url.
web_view.set_data_url(img, mimetype, image_data);
- } else if (!src.has_prefix("data:")) {
- external_images_uri.add(src);
- if (!web_view.load_external_images)
- external_images_info_bar.show();
+ } else if (!src.has_prefix("data:")) { // TODO: Test whether to show images
+ img.set_attribute("src", "remote:" + src);
+ remote_images = true;
}
}
- web_view.set_external_images_uris(external_images_uri);
-
// Now return the whole message.
return set_up_quotes(container.get_inner_html());
} catch (Error e) {
diff --git a/src/client/views/conversation-web-view.vala b/src/client/views/conversation-web-view.vala
index 3038c857..50b0c7f1 100644
--- a/src/client/views/conversation-web-view.vala
+++ b/src/client/views/conversation-web-view.vala
@@ -5,24 +5,14 @@
*/
public class ConversationWebView : WebKit.WebView {
- private const string[] always_loaded_prefixes = {
- "http://www.gravatar.com/avatar/",
- "data:"
- };
-
private const string USER_CSS = "user-message.css";
private const string STYLE_NAME = "STYLE";
- public bool load_external_images { get; private set; default = false; }
-
// HTML element that contains message DIVs.
public WebKit.DOM.HTMLDivElement? container { get; private set; default = null; }
- private Gee.ArrayList? external_images_uri = null;
private FileMonitor? user_style_monitor = null;
- public signal void image_load_requested();
-
public signal void link_selected(string link);
public ConversationWebView() {
@@ -89,71 +79,9 @@ public class ConversationWebView : WebKit.WebView {
}
string? uri = request.get_uri();
- if (!is_always_loaded(uri) && !(is_image(uri) && load_external_images))
+ if (uri.has_prefix("remote:"))
request.set_uri("about:blank");
}
-
- public void set_external_images_uris(Gee.ArrayList uris) {
- external_images_uri = uris;
- }
-
- public bool is_image(string? uri) {
- if (Geary.String.is_empty_or_whitespace(uri))
- return false;
-
- if (uri.has_prefix("data:image/"))
- return true;
-
- // check if external_images_uri is null in case this is called before a page is loaded
- return (external_images_uri != null) ? (uri in external_images_uri) : false;
- }
-
- private bool is_always_loaded(string? uri) {
- if (uri == null)
- return false;
-
- foreach (string prefix in always_loaded_prefixes) {
- if (uri.has_prefix(prefix))
- return true;
- }
-
- return false;
- }
-
- public void apply_load_external_images(bool load_external_images) {
- this.load_external_images = load_external_images;
-
- // Refreshing the images would do nothing in this case--the resource has already been
- // loaded, so no additional resource request will be sent.
- if (load_external_images == false)
- return;
-
- // We can't simply set load_external_images to true before refreshing, then set it back to
- // false afterwards. If one of the images' sources is redirected, an additional resource
- // request will come after we reset load_external_images to false.
- try {
- WebKit.DOM.Document document = get_dom_document();
- WebKit.DOM.NodeList nodes = document.query_selector_all("img");
- for (ulong i = 0; i < nodes.length; i++) {
- WebKit.DOM.Element? element = nodes.item(i) as WebKit.DOM.Element;
- if (element == null)
- continue;
-
- if (!element.has_attribute("src"))
- continue;
-
- string src = element.get_attribute("src");
- if (Geary.String.is_empty_or_whitespace(src) || is_always_loaded(src))
- continue;
-
- // Refresh the image source. Requests are denied when load_external_images
- // is false, so we need to force webkit to send the request again.
- element.set_attribute("src", src);
- }
- } catch (Error err) {
- debug("Error refreshing images: %s", err.message);
- }
- }
private void on_load_finished(WebKit.WebFrame frame) {
// Load the style.
@@ -184,6 +112,7 @@ public class ConversationWebView : WebKit.WebView {
set_icon_src("#email_template .starred .icon", "starred");
set_icon_src("#email_template .unstarred .icon", "non-starred-grey");
set_icon_src("#email_template .attachment.icon", "mail-attachment");
+ set_icon_src("#email_template .close_show_images", "gtk-close");
}
private void load_user_style() {
diff --git a/theming/message-viewer.css b/theming/message-viewer.css
index 48162851..8a6749f1 100644
--- a/theming/message-viewer.css
+++ b/theming/message-viewer.css
@@ -166,6 +166,27 @@ hr {
overflow-y: hidden;
}
+.email .remote_images {
+ display: none;
+ margin: 0 16px;
+ border: 1px solid #999;
+ border-bottom: none;
+ padding: 1em;
+ background: #ffc;
+}
+
+.email .remote_images .close_show_images {
+ float: right;
+ margin-top: -0.67em;
+ margin-right: -0.67em;
+ border: 1px solid #ffc;
+ border-radius: 4px;
+ padding: 2px;
+}
+.email .remote_images .close_show_images:hover {
+ border: 1px solid #a8a887;
+}
+
@media screen {
body {
@@ -213,7 +234,9 @@ hr {
.email.hide .header .field a {
pointer-events: none;
}
-
+ .email:not(.hide) .remote_images.show {
+ display: block;
+ }
.email.compressed {
margin-top: -1px;
height: 10px;
diff --git a/theming/message-viewer.html b/theming/message-viewer.html
index f65e9cde..3ffc48ed 100644
--- a/theming/message-viewer.html
+++ b/theming/message-viewer.html
@@ -18,6 +18,7 @@
+