Prefix CSS classes in message HTML to avoid collisions with HTML messages.

* ui/conversation-web-view.css: Prefix the names of base Geary-internal
  elements with "geary_" to reduce the odds of them colliding with class
  names in HTML messages. Chase the name changes in classes that generate
  them.

* src/client/conversation-viewer/conversation-message.vala
  (ConversationMessage): Use constants for class names and
  WebKit.DOMElement::class_list to add/remove them.
This commit is contained in:
Michael James Gratton 2016-08-11 17:31:08 +10:00
parent b5e7c27c58
commit 5ea4b0a051
3 changed files with 64 additions and 51 deletions

View file

@ -251,7 +251,7 @@ public class ConversationFindBar : Gtk.Layout {
private void switch_to_search_selection_color() { private void switch_to_search_selection_color() {
try { try {
web_view.get_dom_document().get_body().get_class_list().add("search_coloring"); web_view.get_dom_document().get_body().get_class_list().add("geary_search_coloring");
} catch (Error error) { } catch (Error error) {
warning("Error setting body class for search selection coloring: %s", error.message); warning("Error setting body class for search selection coloring: %s", error.message);
} }
@ -259,7 +259,7 @@ public class ConversationFindBar : Gtk.Layout {
private void switch_to_usual_selection_color() { private void switch_to_usual_selection_color() {
try { try {
web_view.get_dom_document().get_body().get_class_list().remove("search_coloring"); web_view.get_dom_document().get_body().get_class_list().remove("geary_search_coloring");
} catch (Error error) { } catch (Error error) {
warning("Error setting body class for search selection coloring: %s", error.message); warning("Error setting body class for search selection coloring: %s", error.message);
} }

View file

@ -78,8 +78,12 @@ public class ConversationMessage : Gtk.Box {
"image/x-xbitmap", "image/x-xbitmap",
"image/x-xbm" "image/x-xbm"
}; };
private const string REPLACED_IMAGE_CLASS = "replaced_inline_image"; private const string QUOTE_CONTAINER_CLASS = "geary_quote_container";
private const string DATA_IMAGE_CLASS = "data_inline_image"; private const string QUOTE_CONTROLLABLE_CLASS = "controllable";
private const string QUOTE_HIDE_CLASS = "hide";
private const string SIGNATURE_CONTAINER_CLASS = "geary_signature";
private const string REPLACED_IMAGE_CLASS = "geary_replaced_inline_image";
private const string DATA_IMAGE_CLASS = "geary_data_inline_image";
private const int MAX_INLINE_IMAGE_MAJOR_DIM = 1024; private const int MAX_INLINE_IMAGE_MAJOR_DIM = 1024;
private const float QUOTE_SIZE_THRESHOLD = 2.0f; private const float QUOTE_SIZE_THRESHOLD = 2.0f;
@ -421,14 +425,22 @@ public class ConversationMessage : Gtk.Box {
error.message); error.message);
} }
} }
Util.DOM.bind_event(this.web_view, "html", "contextmenu", Util.DOM.bind_event(
(Callback) on_context_menu, this); this.web_view, "html", "contextmenu",
Util.DOM.bind_event(this.web_view, "body a", "click", (Callback) on_context_menu, this
(Callback) on_link_clicked, this); );
Util.DOM.bind_event(this.web_view, ".quote_container > .shower", "click", Util.DOM.bind_event(
(Callback) on_show_quote_clicked, this); this.web_view, "body a", "click",
Util.DOM.bind_event(this.web_view, ".quote_container > .hider", "click", (Callback) on_link_clicked, this
(Callback) on_hide_quote_clicked, this); );
Util.DOM.bind_event(
this.web_view, ".%s > .shower".printf(QUOTE_CONTAINER_CLASS),
"click",
(Callback) on_show_quote_clicked, this);
Util.DOM.bind_event(
this.web_view, ".%s > .hider".printf(QUOTE_CONTAINER_CLASS),
"click",
(Callback) on_hide_quote_clicked, this);
// XXX Not actually true since remote images will // XXX Not actually true since remote images will
// still be loading. // still be loading.
@ -500,7 +512,7 @@ public class ConversationMessage : Gtk.Box {
// Remove the chrome we put around quotes, leaving // Remove the chrome we put around quotes, leaving
// only the blockquote element. // only the blockquote element.
WebKit.DOM.NodeList quotes = WebKit.DOM.NodeList quotes =
dummy.query_selector_all(".quote_container"); dummy.query_selector_all("." + QUOTE_CONTAINER_CLASS);
for (int i = 0; i < quotes.length; i++) { for (int i = 0; i < quotes.length; i++) {
WebKit.DOM.Element div = (WebKit.DOM.Element) quotes.item(i); WebKit.DOM.Element div = (WebKit.DOM.Element) quotes.item(i);
WebKit.DOM.Element blockquote = div.query_selector("blockquote"); WebKit.DOM.Element blockquote = div.query_selector("blockquote");
@ -821,10 +833,6 @@ public class ConversationMessage : Gtk.Box {
continue; continue;
} }
// parent
// quote_container
// blockquote
// sibling
WebKit.DOM.Element quote_container = create_quote_container(); WebKit.DOM.Element quote_container = create_quote_container();
Util.DOM.select(quote_container, ".quote").append_child(blockquote_node); Util.DOM.select(quote_container, ".quote").append_child(blockquote_node);
if (next_sibling == null) { if (next_sibling == null) {
@ -878,7 +886,7 @@ public class ConversationMessage : Gtk.Box {
// and the ALT to its filename, if supplied // and the ALT to its filename, if supplied
img.remove_attribute("src"); // Work around a WebKitGTK+ crash. Bug 764152 img.remove_attribute("src"); // Work around a WebKitGTK+ crash. Bug 764152
img.set_attribute("src", Util.DOM.assemble_data_uri(mimetype, image_content)); img.set_attribute("src", Util.DOM.assemble_data_uri(mimetype, image_content));
img.set_attribute("class", DATA_IMAGE_CLASS); img.class_list.add(DATA_IMAGE_CLASS);
if (!Geary.String.is_empty(filename)) if (!Geary.String.is_empty(filename))
img.set_attribute("alt", filename); img.set_attribute("alt", filename);
@ -911,12 +919,12 @@ public class ConversationMessage : Gtk.Box {
return text; return text;
} }
} }
private WebKit.DOM.HTMLElement create_quote_container() throws Error { private WebKit.DOM.HTMLElement create_quote_container() throws Error {
WebKit.DOM.HTMLElement quote_container = web_view.create("div"); WebKit.DOM.HTMLElement quote_container = web_view.create("div");
quote_container.set_attribute( quote_container.class_list.add(QUOTE_CONTAINER_CLASS);
"class", "quote_container controllable hide" quote_container.class_list.add(QUOTE_CONTROLLABLE_CLASS);
); quote_container.class_list.add(QUOTE_HIDE_CLASS);
// New lines are preserved within blockquotes, so this string // New lines are preserved within blockquotes, so this string
// needs to be new-line free. // needs to be new-line free.
quote_container.set_inner_html("""<div class="shower"><input type="button" value=" " /></div><div class="hider"><input type="button" value=" " /></div><div class="quote"></div>"""); quote_container.set_inner_html("""<div class="shower"><input type="button" value=" " /></div><div class="hider"><input type="button" value=" " /></div><div class="quote"></div>""");
@ -952,7 +960,7 @@ public class ConversationMessage : Gtk.Box {
WebKit.DOM.Node elem = div_list.item(i) as WebKit.DOM.Node; WebKit.DOM.Node elem = div_list.item(i) as WebKit.DOM.Node;
WebKit.DOM.Element parent = elem.get_parent_element(); WebKit.DOM.Element parent = elem.get_parent_element();
WebKit.DOM.HTMLElement signature_container = web_view.create("div"); WebKit.DOM.HTMLElement signature_container = web_view.create("div");
signature_container.set_attribute("class", "signature"); signature_container.class_list.add(SIGNATURE_CONTAINER_CLASS);
do { do {
// Get its sibling _before_ we move it into the signature div. // Get its sibling _before_ we move it into the signature div.
WebKit.DOM.Node? sibling = elem.get_next_sibling(); WebKit.DOM.Node? sibling = elem.get_next_sibling();
@ -963,8 +971,9 @@ public class ConversationMessage : Gtk.Box {
} }
private void unset_controllable_quotes(WebKit.DOM.HTMLElement element) throws GLib.Error { private void unset_controllable_quotes(WebKit.DOM.HTMLElement element) throws GLib.Error {
WebKit.DOM.NodeList quote_list = WebKit.DOM.NodeList quote_list = element.query_selector_all(
element.query_selector_all(".quote_container.controllable"); ".%s.%s".printf(QUOTE_CONTAINER_CLASS, QUOTE_CONTROLLABLE_CLASS)
);
for (int i = 0; i < quote_list.length; ++i) { for (int i = 0; i < quote_list.length; ++i) {
WebKit.DOM.Element quote_container = quote_list.item(i) as WebKit.DOM.Element; WebKit.DOM.Element quote_container = quote_list.item(i) as WebKit.DOM.Element;
long outer_client_height = quote_container.client_height; long outer_client_height = quote_container.client_height;
@ -974,8 +983,8 @@ public class ConversationMessage : Gtk.Box {
// substantial amount hidden. // substantial amount hidden.
if (scroll_height > 0 && if (scroll_height > 0 &&
scroll_height <= outer_client_height * QUOTE_SIZE_THRESHOLD) { scroll_height <= outer_client_height * QUOTE_SIZE_THRESHOLD) {
quote_container.class_list.remove("controllable"); quote_container.class_list.remove(QUOTE_CONTROLLABLE_CLASS);
quote_container.class_list.remove("hide"); quote_container.class_list.remove(QUOTE_HIDE_CLASS);
} }
} }
} }
@ -1108,7 +1117,9 @@ public class ConversationMessage : Gtk.Box {
private static void on_show_quote_clicked(WebKit.DOM.Element element, private static void on_show_quote_clicked(WebKit.DOM.Element element,
WebKit.DOM.Event event) { WebKit.DOM.Event event) {
try { try {
((WebKit.DOM.HTMLElement) element.parent_node).class_list.remove("hide"); ((WebKit.DOM.HTMLElement) element.parent_node).class_list.remove(
QUOTE_HIDE_CLASS
);
} catch (Error error) { } catch (Error error) {
warning("Error showing quote: %s", error.message); warning("Error showing quote: %s", error.message);
} }
@ -1118,7 +1129,9 @@ public class ConversationMessage : Gtk.Box {
WebKit.DOM.Event event, WebKit.DOM.Event event,
ConversationMessage message) { ConversationMessage message) {
try { try {
((WebKit.DOM.HTMLElement) element.parent_node).class_list.add("hide"); ((WebKit.DOM.HTMLElement) element.parent_node).class_list.add(
QUOTE_HIDE_CLASS
);
message.web_view.queue_resize(); message.web_view.queue_resize();
} catch (Error error) { } catch (Error error) {
warning("Error toggling quote: %s", error.message); warning("Error toggling quote: %s", error.message);

View file

@ -66,19 +66,19 @@ pre {
* Message chrome style. * Message chrome style.
*/ */
.signature { .geary_signature {
color: #777; color: #777;
display: inline; display: inline;
} }
.signature a, .geary_signature a,
.quote_container a { .geary_quote_container a {
color: #5fb2e7; color: #5fb2e7;
} }
@media screen { @media screen {
.replaced_inline_image { .geary_replaced_inline_image {
display: block; display: block;
max-width: 100%; max-width: 100%;
margin-top: 1em; margin-top: 1em;
@ -86,7 +86,7 @@ pre {
/* Inline collapsable quote blocks */ /* Inline collapsable quote blocks */
.quote_container { .geary_quote_container {
position: relative; position: relative;
margin: 0.5em 0; margin: 0.5em 0;
border-radius: 4px; border-radius: 4px;
@ -94,11 +94,11 @@ pre {
color: #303030; color: #303030;
background-color: #e8e8e8;/* recv-quoted */ background-color: #e8e8e8;/* recv-quoted */
} }
.sent .quote_container { .geary_sent .geary_quote_container {
background-color: #e8e8e8;/* sent-quoted */ background-color: #e8e8e8;/* sent-quoted */
} }
.quote_container > .quote { .geary_quote_container > .quote {
position: relative; position: relative;
margin: 0; margin: 0;
border: 0; border: 0;
@ -106,18 +106,18 @@ pre {
overflow: hidden; overflow: hidden;
z-index: 0; z-index: 0;
} }
.quote_container.controllable.hide > .quote { .geary_quote_container.controllable.hide > .quote {
/* Use a fraction value to cut the last visible line off half way. */ /* Use a fraction value to cut the last visible line off half way. */
max-height: 7.75em; max-height: 7.75em;
} }
.quote_container.controllable > .quote > blockquote { .geary_quote_container.controllable > .quote > blockquote {
/* Add space between the quote and the hider button */ /* Add space between the quote and the hider button */
margin-bottom: 18px; margin-bottom: 18px;
} }
.quote_container > .shower, .geary_quote_container > .shower,
.quote_container > .hider { .geary_quote_container > .hider {
position: absolute; position: absolute;
display: none; display: none;
left: 0; left: 0;
@ -128,30 +128,30 @@ pre {
-webkit-user-drag: none; -webkit-user-drag: none;
} }
.quote_container > .shower > input, .geary_quote_container > .shower > input,
.quote_container > .hider > input { .geary_quote_container > .hider > input {
width: 100%; width: 100%;
height: 16px; height: 16px;
padding: 0; padding: 0;
font-size: 8px; /* Absolute size in pixels for graphics */ font-size: 8px; /* Absolute size in pixels for graphics */
color: #888; color: #888;
} }
.quote_container > .shower:hover > input, .geary_quote_container > .shower:hover > input,
.quote_container > .hider:hover > input { .geary_quote_container > .hider:hover > input {
color: #000; color: #000;
} }
.quote_container.controllable.hide > .hider { .geary_quote_container.controllable.hide > .hider {
display: none; display: none;
} }
.quote_container.controllable.hide > .shower, .geary_quote_container.controllable.hide > .shower,
.quote_container.controllable > .hider { .geary_quote_container.controllable > .hider {
display: block; display: block;
} }
/* Highlight search terms */ /* Highlight search terms */
.search_coloring *::selection { .geary_search_coloring *::selection {
background-color: #00ddff; background-color: #00ddff;
} }
} }
@ -161,14 +161,14 @@ pre {
background-color: white !important; background-color: white !important;
} }
#part_container { #geary_part_container {
display: none !important; display: none !important;
} }
#print_container { #geary_print_container {
display: inline-block !important; display: inline-block !important;
background-color: white !important; background-color: white !important;
} }
#print_container .preview { #geary_print_container .preview {
display: none !important; display: none !important;
} }
} }