Per-email enabling of remote images; fix #6649
This commit is contained in:
parent
35f2c12180
commit
66dd62a23c
5 changed files with 89 additions and 116 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
<input type="button" value="%s" class="show_images" />""".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 <img> 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<string> external_images_uri = new Gee.ArrayList<string>();
|
||||
// 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) {
|
||||
|
|
|
|||
|
|
@ -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<string>? 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<string> 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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<div class="header"></div>
|
||||
<div class="preview"></div>
|
||||
</div>
|
||||
<div class="remote_images"><img class="close_show_images" /></div>
|
||||
<div class="body"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue