From 66dd62a23cd3a90593198b44ff9980f9428f52e2 Mon Sep 17 00:00:00 2001 From: Robert Schroll Date: Mon, 22 Apr 2013 14:14:11 -0700 Subject: [PATCH] Per-email enabling of remote images; fix #6649 --- src/client/geary-controller.vala | 1 - src/client/views/conversation-viewer.vala | 103 ++++++++++++-------- src/client/views/conversation-web-view.vala | 75 +------------- theming/message-viewer.css | 25 ++++- theming/message-viewer.html | 1 + 5 files changed, 89 insertions(+), 116 deletions(-) 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 @@
+