[geary/wip/728002-webkit2: 38/46] Re-implement message HTML cleaning in JS in the web extension for WK2.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/728002-webkit2: 38/46] Re-implement message HTML cleaning in JS in the web extension for WK2.
- Date: Thu, 1 Dec 2016 23:42:06 +0000 (UTC)
commit dd8bc74bba9ef46ac60897b121eae92a9cd1b284
Author: Michael James Gratton <mike vee net>
Date: Sun Nov 27 21:03:52 2016 +1100
Re-implement message HTML cleaning in JS in the web extension for WK2.
* ui/conversation-web-view.js: New script, port old HTML cleaning code in
vala to Javascript as new subclass of PageState. Instantiate that on
page load.
* src/client/conversation-viewer/conversation-web-view.vala
(ConversationWebView): Load and add new JS script for conversations.
* src/client/web-process/util-conversation.vala (Util.Conversation):
Remove migrated and obsolete code.
* ui/client-web-view.js (PageState): Allow on-load behaviour to be
overridden in subclasses.
* ui/CMakeLists.txt: Include new JS script.
* ui/conversation-web-view.css: Chase CSS class name changes.
src/client/application/geary-controller.vala | 2 +-
.../conversation-viewer/conversation-web-view.vala | 13 +-
src/client/web-process/util-conversation.vala | 186 --------------------
ui/CMakeLists.txt | 1 +
ui/client-web-view.js | 12 +-
ui/conversation-web-view.css | 35 ++--
ui/conversation-web-view.js | 136 ++++++++++++++
7 files changed, 169 insertions(+), 216 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 544eadc..7184dbd 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -210,7 +210,7 @@ public class GearyController : Geary.BaseObject {
// Load web view resources
try {
ClientWebView.load_scripts(this.application);
- ConversationWebView.load_stylehseets(this.application);
+ ConversationWebView.load_resources(this.application);
} catch (Error err) {
error("Error loading web resources: %s", err.message);
}
diff --git a/src/client/conversation-viewer/conversation-web-view.vala
b/src/client/conversation-viewer/conversation-web-view.vala
index f112444..bbfc951 100644
--- a/src/client/conversation-viewer/conversation-web-view.vala
+++ b/src/client/conversation-viewer/conversation-web-view.vala
@@ -12,9 +12,12 @@ public class ConversationWebView : ClientWebView {
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_stylehseets(GearyApplication app)
+ public static void load_resources(GearyApplication app)
throws Error {
+ ConversationWebView.app_script =
+ ClientWebView.load_app_script(app, "conversation-web-view.js");
ConversationWebView.app_stylesheet =
ClientWebView.load_app_stylesheet(app, "conversation-web-view.css");
ConversationWebView.user_stylesheet =
@@ -23,12 +26,12 @@ public class ConversationWebView : ClientWebView {
public ConversationWebView() {
- WebKit.UserContentManager manager = new WebKit.UserContentManager();
- manager.add_style_sheet(ConversationWebView.app_stylesheet);
+ base();
+ this.user_content_manager.add_script(ConversationWebView.app_script);
+ this.user_content_manager.add_style_sheet(ConversationWebView.app_stylesheet);
if (ConversationWebView.user_stylesheet != null) {
- manager.add_style_sheet(ConversationWebView.user_stylesheet);
+ this.user_content_manager.add_style_sheet(ConversationWebView.user_stylesheet);
}
- base(manager);
}
public void clean_and_load(string html) {
diff --git a/src/client/web-process/util-conversation.vala b/src/client/web-process/util-conversation.vala
index 474d515..04c95e8 100644
--- a/src/client/web-process/util-conversation.vala
+++ b/src/client/web-process/util-conversation.vala
@@ -8,143 +8,12 @@
namespace Util.Conversation {
- private const string SIGNATURE_CONTAINER_CLASS = "geary_signature";
-
private const string QUOTE_CONTAINER_CLASS = "geary_quote_container";
private const string QUOTE_CONTROLLABLE_CLASS = "controllable";
private const string QUOTE_HIDE_CLASS = "hide";
private const float QUOTE_SIZE_THRESHOLD = 2.0f;
- public double get_preferred_height(WebKit.WebPage page) {
- WebKit.DOM.Element html = page.get_dom_document().get_document_element();
- double offset_height = html.offset_height;
- double offset_width = html.offset_width;
- double px = offset_width * offset_height;
-
- const double MAX_LEN = 15.0 * 1000;
- const double MAX_PX = 10.0 * 1000 * 1000;
-
- // If the offset_width is very small, the offset_height will
- // likely be bogus, so just pretend we have no height for the
- // moment. WebKitGTK seems to report an offset width of 1 in
- // these cases.
- if (offset_width > 1) {
- if (offset_height > MAX_LEN || px > MAX_PX) {
- double new_height = double.min(MAX_LEN, MAX_PX / offset_width);
- debug("Clamping window height to: %f, current size: %fx%f (%fpx)",
- new_height, offset_width, offset_height, px);
- offset_height = new_height;
- }
- } else {
- offset_height = 0;
- }
-
- return offset_height;
- }
-
- public string clean_html_markup(WebKit.WebPage page, string text, Geary.RFC822.Message message) {
- try {
- WebKit.DOM.HTMLElement html = (WebKit.DOM.HTMLElement)
- page.get_dom_document().document_element;
-
- // If the message has a HTML element, get its inner
- // markup. We can't just set this on a temp container div
- // (the old approach) using set_inner_html() will refuse
- // to parse any HTML, HEAD and BODY elements that are out
- // of place in the structure. We can't use
- // set_outer_html() on the document element since it
- // throws an error.
- GLib.Regex html_regex = new GLib.Regex("<html([^>]*)>(.*)</html>",
- GLib.RegexCompileFlags.DOTALL);
- GLib.MatchInfo matches;
- if (html_regex.match(text, 0, out matches)) {
- // Set the existing HTML element's content. Here, HEAD
- // and BODY elements will be parsed fine.
- html.set_inner_html(matches.fetch(2));
- // Copy email HTML element attrs across to the
- // existing HTML element
- string attrs = matches.fetch(1);
- if (attrs != "") {
- WebKit.DOM.HTMLElement container = create(page, "div");
- container.set_inner_html(@"<div$attrs></div>");
- WebKit.DOM.HTMLElement? attr_element =
- Util.DOM.select(container, "div");
- WebKit.DOM.NamedNodeMap html_attrs =
- attr_element.get_attributes();
- for (int i = 0; i < html_attrs.get_length(); i++) {
- WebKit.DOM.Node attr = html_attrs.item(i);
- html.set_attribute(attr.node_name, attr.text_content);
- }
- }
- } else {
- html.set_inner_html(text);
- }
-
- // Set dir="auto" if not already set possibly get a
- // slightly better RTL experience.
- string? dir = html.get_dir();
- if (dir == null || dir.length == 0) {
- html.set_dir("auto");
- }
-
- // Get all the top level block quotes and stick them into a hide/show controller.
- WebKit.DOM.NodeList blockquote_list = html.query_selector_all("blockquote");
- for (int i = 0; i < blockquote_list.length; ++i) {
- // Get the nodes we need.
- WebKit.DOM.Node blockquote_node = blockquote_list.item(i);
- WebKit.DOM.Node? next_sibling = blockquote_node.get_next_sibling();
- WebKit.DOM.Node parent = blockquote_node.get_parent_node();
-
- // Make sure this is a top level blockquote.
- if (Util.DOM.node_is_child_of(blockquote_node, "BLOCKQUOTE")) {
- continue;
- }
-
- WebKit.DOM.Element quote_container = create_quote_container(page);
- Util.DOM.select(quote_container, ".quote").append_child(blockquote_node);
- if (next_sibling == null) {
- parent.append_child(quote_container);
- } else {
- parent.insert_before(quote_container, next_sibling);
- }
- }
-
- // Now look for the signature.
- wrap_html_signature(page, ref html);
-
- // Now return the whole message.
- return html.get_outer_html();
- } catch (Error e) {
- debug("Error modifying HTML message: %s", e.message);
- return text;
- }
- }
-
- public void unset_controllable_quotes(WebKit.WebPage page)
- throws Error {
- WebKit.DOM.HTMLElement html =
- page.get_dom_document().document_element as WebKit.DOM.HTMLElement;
- if (html != null) {
- WebKit.DOM.NodeList quote_list = html.query_selector_all(
- ".%s.%s".printf(QUOTE_CONTAINER_CLASS, QUOTE_CONTROLLABLE_CLASS)
- );
- for (int i = 0; i < quote_list.length; ++i) {
- WebKit.DOM.Element quote_container = quote_list.item(i) as WebKit.DOM.Element;
- double outer_client_height = quote_container.client_height;
- long scroll_height = quote_container.query_selector(".quote").scroll_height;
- // If the message is hidden, scroll_height will be
- // 0. Otherwise, unhide the full quote if there is not a
- // substantial amount hidden.
- if (scroll_height > 0 &&
- scroll_height <= outer_client_height * QUOTE_SIZE_THRESHOLD) {
- //quote_container.class_list.remove(QUOTE_CONTROLLABLE_CLASS);
- //quote_container.class_list.remove(QUOTE_HIDE_CLASS);
- }
- }
- }
- }
-
public string? get_selection_for_quoting(WebKit.WebPage page) {
string? quote = null;
// WebKit.DOM.Document document = page.get_dom_document();
@@ -206,59 +75,4 @@ namespace Util.Conversation {
return value;
}
- private WebKit.DOM.HTMLElement create(WebKit.WebPage page, string name)
- throws Error {
- return page.get_dom_document().create_element(name) as WebKit.DOM.HTMLElement;
- }
-
- private WebKit.DOM.HTMLElement create_quote_container(WebKit.WebPage page) throws Error {
- WebKit.DOM.HTMLElement quote_container = create(page, "div");
- // quote_container.class_list.add(QUOTE_CONTAINER_CLASS);
- // 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
- // 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>""");
- return quote_container;
- }
-
- private void wrap_html_signature(WebKit.WebPage page, ref WebKit.DOM.HTMLElement container) throws Error
{
- // Most HTML signatures fall into one of these designs which are handled by this method:
- //
- // 1. GMail: <div>-- </div>$SIGNATURE
- // 2. GMail Alternate: <div><span>-- </span></div>$SIGNATURE
- // 3. Thunderbird: <div>-- <br>$SIGNATURE</div>
- //
- WebKit.DOM.NodeList div_list = container.query_selector_all("div,span,p");
- int i = 0;
- Regex sig_regex = new Regex("^--\\s*$");
- Regex alternate_sig_regex = new Regex("^--\\s*(?:<br|\\R)");
- for (; i < div_list.length; ++i) {
- // Get the div and check that it starts a signature block and is not inside a quote.
- WebKit.DOM.HTMLElement div = div_list.item(i) as WebKit.DOM.HTMLElement;
- string inner_html = div.get_inner_html();
- if ((sig_regex.match(inner_html) || alternate_sig_regex.match(inner_html)) &&
- !Util.DOM.node_is_child_of(div, "BLOCKQUOTE")) {
- break;
- }
- }
-
- // If we have a signature, move it and all of its following siblings that are not quotes
- // inside a signature div.
- if (i == div_list.length) {
- return;
- }
- WebKit.DOM.Node elem = div_list.item(i) as WebKit.DOM.Node;
- WebKit.DOM.Element parent = elem.get_parent_element();
- WebKit.DOM.HTMLElement signature_container = create(page, "div");
- //signature_container.class_list.add(SIGNATURE_CONTAINER_CLASS);
- do {
- // Get its sibling _before_ we move it into the signature div.
- WebKit.DOM.Node? sibling = elem.get_next_sibling();
- signature_container.append_child(elem);
- elem = sibling;
- } while (elem != null);
- parent.append_child(signature_container);
- }
-
}
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index a47e1b6..65d416c 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -17,6 +17,7 @@ set(RESOURCE_LIST
STRIPBLANKS "conversation-message-menus.ui"
STRIPBLANKS "conversation-viewer.ui"
"conversation-web-view.css"
+ "conversation-web-view.js"
STRIPBLANKS "edit_alternate_emails.glade"
STRIPBLANKS "empty-placeholder.ui"
STRIPBLANKS "find_bar.glade"
diff --git a/ui/client-web-view.js b/ui/client-web-view.js
index f4105f9..13000cb 100644
--- a/ui/client-web-view.js
+++ b/ui/client-web-view.js
@@ -15,16 +15,19 @@ var PageState = function() {
PageState.prototype = {
init: function() {
this.allowRemoteImages = false;
- this.loaded = false;
+ this.is_loaded = false;
var state = this;
var timeoutId = window.setInterval(function() {
state.preferredHeightChanged();
- if (state.loaded) {
+ if (state.is_loaded) {
window.clearTimeout(timeoutId);
}
}, 50);
},
+ loaded: function() {
+ this.is_loaded = true;
+ },
loadRemoteImages: function() {
this.allowRemoteImages = true;
var images = document.getElementsByTagName("IMG");
@@ -51,8 +54,3 @@ PageState.prototype = {
window.webkit.messageHandlers.selectionChanged.postMessage(has_selection);
}
};
-
-var geary = new PageState();
-window.onload = function() {
- geary.loaded = true;
-};
diff --git a/ui/conversation-web-view.css b/ui/conversation-web-view.css
index 5405c46..d048993 100644
--- a/ui/conversation-web-view.css
+++ b/ui/conversation-web-view.css
@@ -70,13 +70,13 @@ pre {
* Message chrome style.
*/
-.geary_signature {
+.geary-signature {
color: #777;
display: inline;
}
-.geary_signature a,
-.geary_quote_container a {
+.geary-signature a,
+.geary-quote-container a {
color: #5fb2e7;
}
@@ -90,7 +90,7 @@ pre {
/* Inline collapsable quote blocks */
- .geary_quote_container {
+ .geary-quote-container {
position: relative;
/* Split 1em of top/bottom margin between here and the default
blockquote style, so if a message specifies 0px margin and padding
@@ -102,11 +102,11 @@ pre {
color: #303030;
background-color: #e8e8e8;/* recv-quoted */
}
- .geary_sent .geary_quote_container {
+ .geary-sent .geary-quote-container {
background-color: #e8e8e8;/* sent-quoted */
}
- .geary_quote_container > .quote {
+ .geary-quote-container > .geary-quote {
position: relative;
padding: 0;
border: 0;
@@ -114,18 +114,18 @@ pre {
overflow: hidden;
z-index: 0;
}
- .geary_quote_container.controllable.hide > .quote {
+ .geary-quote-container.geary-controllable.geary-hide > .geary-quote {
/* Use a fraction value to cut the last visible line off half way. */
max-height: 7.75em;
}
- .geary_quote_container.controllable > .quote > blockquote {
+ .geary-quote-container.geary-controllable > .geary-quote > blockquote {
/* Add space between the quote and the hider button */
margin-bottom: 18px;
}
- .geary_quote_container > .shower,
- .geary_quote_container > .hider {
+ .geary-quote-container > .geary-shower,
+ .geary-quote-container > .geary-hider {
position: absolute;
display: none;
left: 0;
@@ -136,24 +136,25 @@ pre {
-webkit-user-drag: none;
}
- .geary_quote_container > .shower > input,
- .geary_quote_container > .hider > input {
+ .geary-quote-container > .geary-shower > input,
+ .geary-quote-container > .geary-hider > input {
+ display: block;
width: 100%;
height: 16px;
padding: 0;
font-size: 8px; /* Absolute size in pixels for graphics */
color: #888;
}
- .geary_quote_container > .shower:hover > input,
- .geary_quote_container > .hider:hover > input {
+ .geary-quote-container > .geary-shower:hover > input,
+ .geary-quote-container > .geary-hider:hover > input {
color: #000;
}
- .geary_quote_container.controllable.hide > .hider {
+ .geary-quote-container.geary-controllable.geary-hide > .geary-hider {
display: none;
}
- .geary_quote_container.controllable.hide > .shower,
- .geary_quote_container.controllable > .hider {
+ .geary-quote-container.geary-controllable.geary-hide > .geary-shower,
+ .geary-quote-container.geary-controllable > .geary-hider {
display: block;
}
diff --git a/ui/conversation-web-view.js b/ui/conversation-web-view.js
new file mode 100644
index 0000000..15db7fb
--- /dev/null
+++ b/ui/conversation-web-view.js
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2016 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * Application logic for ConversationWebView.
+ */
+var ConversationPageState = function() {
+ this.init.apply(this, arguments);
+};
+ConversationPageState.prototype = {
+ __proto__: PageState.prototype,
+ init: function() {
+ PageState.prototype.init.apply(this, []);
+ },
+ loaded: function() {
+ this.updateDirection();
+ this.createControllableQuotes();
+ this.wrapSignature();
+ // Call after so we continue to a preferred size update after
+ // munging the HTML above.
+ PageState.prototype.loaded.apply(this, []);
+ },
+ /**
+ * Set dir="auto" if not already set.
+ *
+ * This should provide a slightly better RTL experience.
+ */
+ updateDirection: function() {
+ var dir = document.documentElement.dir;
+ if (dir == null || dir.trim() == "") {
+ document.documentElement.dir = "auto";
+ }
+ },
+ /**
+ * Add top level blockquotes to hide/show container.
+ */
+ createControllableQuotes: function() {
+ var blockquoteList = document.documentElement.querySelectorAll("blockquote");
+ for (var i = 0; i < blockquoteList.length; ++i) {
+ var blockquote = blockquoteList.item(i);
+ var nextSibling = blockquote.nextSibling;
+ var parent = blockquote.parentNode;
+
+ // Only insert into a quote container if the element is a
+ // top level blockquote
+ if (!ConversationPageState.isDescendantOf(blockquote, "BLOCKQUOTE")) {
+ var quoteContainer = document.createElement("DIV");
+ quoteContainer.classList.add("geary-quote-container");
+
+ // Only make it controllable if the quote is tall enough
+ if (blockquote.offsetHeight > 50) {
+ quoteContainer.classList.add("geary-controllable");
+ quoteContainer.classList.add("geary-hide");
+ }
+ // New lines are preserved within blockquotes, so this
+ // string needs to be new-line free.
+ quoteContainer.innerHTML =
+ "<div class=\"geary-shower\">" +
+ "<input type=\"button\" value=\"▼ ▼ ▼\" />" +
+ "</div>" +
+ "<div class=\"geary-hider\">" +
+ "<input type=\"button\" value=\"▲ ▲ ▲\" />" +
+ "</div>";
+
+ var quoteDiv = document.createElement("DIV");
+ quoteDiv.classList.add("geary-quote");
+ quoteDiv.appendChild(blockquote);
+
+ quoteContainer.appendChild(quoteDiv);
+ parent.insertBefore(quoteContainer, nextSibling);
+ }
+ }
+ },
+ /**
+ * Look for and wrap a signature.
+ *
+ * Most HTML signatures fall into one
+ * of these designs which are handled by this method:
+ *
+ * 1. GMail: <div>-- </div>$SIGNATURE
+ * 2. GMail Alternate: <div><span>-- </span></div>$SIGNATURE
+ * 3. Thunderbird: <div>-- <br>$SIGNATURE</div>
+ *
+ */
+ wrapSignature: function() {
+ var possibleSigs = document.documentElement.querySelectorAll("div,span,p");
+ var i = 0;
+ var sigRegex = new RegExp("^--\\s*$");
+ var alternateSigRegex = new RegExp("^--\\s*(?:<br|\\R)");
+ for (; i < possibleSigs.length; ++i) {
+ // Get the div and check that it starts a signature block
+ // and is not inside a quote.
+ var div = possibleSigs.item(i);
+ var innerHTML = div.innerHTML;
+ if ((sigRegex.test(innerHTML) || alternateSigRegex.test(innerHTML)) &&
+ !ConversationPageState.isDescendantOf(div, "BLOCKQUOTE")) {
+ break;
+ }
+ }
+ // If we have a signature, move it and all of its following
+ // siblings that are not quotes inside a signature div.
+ if (i < possibleSigs.length) {
+ var elem = possibleSigs.item(i);
+ var parent = elem.parentNode;
+ var signatureContainer = document.createElement("DIV");
+ signatureContainer.classList.add("geary-signature");
+ do {
+ // Get its sibling _before_ we move it into the signature div.
+ var sibling = elem.nextSibling;
+ signatureContainer.appendChild(elem);
+ elem = sibling;
+ } while (elem != null);
+ parent.appendChild(signatureContainer);
+ }
+ }
+};
+
+ConversationPageState.isDescendantOf = function(node, ancestorTag) {
+ var ancestor = node.parentNode;
+ while (ancestor != null) {
+ if (ancestor.tagName == ancestorTag) {
+ return true;
+ }
+ ancestor = ancestor.parentNode;
+ }
+ return false;
+};
+
+var geary = new ConversationPageState();
+window.onload = function() {
+ geary.loaded();
+};
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]