1 # include <iostream>
2 # include <thread>
3 # include <functional>
4 
5 # include "tvextension.hh"
6 
7 # include <webkit2/webkit-web-extension.h>
8 
9 # include <gmodule.h>
10 # include <glib.h>
11 # include <glibmm.h>
12 # include <giomm.h>
13 # include <giomm/socket.h>
14 # include <gtkmm.h>
15 
16 /* boost::log */
17 # include <boost/log/core.hpp>
18 # include <boost/log/utility/setup/file.hpp>
19 # include <boost/log/utility/setup/console.hpp>
20 # include <boost/log/utility/setup/common_attributes.hpp>
21 # include <boost/log/sinks/text_file_backend.hpp>
22 # include <boost/log/sources/severity_logger.hpp>
23 # include <boost/log/sources/record_ostream.hpp>
24 # include <boost/log/utility/setup/filter_parser.hpp>
25 # include <boost/log/utility/setup/formatter_parser.hpp>
26 # include <boost/date_time/posix_time/posix_time_types.hpp>
27 # include <boost/log/expressions.hpp>
28 # include <boost/log/trivial.hpp>
29 # include <boost/log/support/date_time.hpp>
30 # include <boost/log/sinks/syslog_backend.hpp>
31 # define LOG(x) BOOST_LOG_TRIVIAL(x)
32 # define warn warning
33 
34 # include "modes/thread_view/webextension/ae_protocol.hh"
35 # include "modes/thread_view/webextension/dom_utils.hh"
36 # include "utils/ustring_utils.hh"
37 # include "messages.pb.h"
38 
39 namespace logging   = boost::log;
40 namespace keywords  = boost::log::keywords;
41 namespace expr      = boost::log::expressions;
42 
43 using namespace Astroid;
44 
45 extern "C" {/*{{{*/
46 
47 static void
web_page_created_callback(WebKitWebExtension * extension,WebKitWebPage * web_page,gpointer user_data)48 web_page_created_callback (WebKitWebExtension *extension,
49                            WebKitWebPage      *web_page,
50                            gpointer            user_data )
51 {
52 
53   g_signal_connect (web_page, "send-request",
54       G_CALLBACK (web_page_send_request),
55       NULL);
56 
57   ext->page_created (extension, web_page, user_data);
58 }
59 
web_page_send_request(WebKitWebPage * web_page,WebKitURIRequest * request,WebKitURIResponse * response,gpointer user_data)60 bool web_page_send_request ( WebKitWebPage    *  web_page,
61                              WebKitURIRequest *  request,
62                              WebKitURIResponse * response,
63                              gpointer            user_data)
64 {
65   return ext->send_request (web_page, request, response, user_data);
66 }
67 
68 G_MODULE_EXPORT void
webkit_web_extension_initialize_with_user_data(WebKitWebExtension * extension,gpointer pipes)69 webkit_web_extension_initialize_with_user_data (
70     WebKitWebExtension *extension,
71     gpointer pipes)
72 {
73 
74   /* IMPORTANT: We assume that there will only be one extension instance
75    * per web page. That means that there can only be one page in each web view,
76    * and each web view must use its own process. */
77 
78   ext = new AstroidExtension (extension, pipes);
79 
80   g_signal_connect (extension, "page-created",
81       G_CALLBACK (web_page_created_callback),
82       NULL);
83 }
84 
85 }/*}}}*/
86 
init_console_log()87 void AstroidExtension::init_console_log () {
88   /* log to console */
89   logging::formatter format =
90                 expr::stream
91                     << "["
92                     << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%H:%M:%S")
93                     << "] [" << expr::attr <boost::log::attributes::current_thread_id::value_type>("ThreadID")
94                     << "] [E] [" << logging::trivial::severity
95                     << "] " << expr::smessage
96             ;
97 
98   logging::add_console_log ()->set_formatter (format);
99 }
100 
init_sys_log()101 void AstroidExtension::init_sys_log () {
102   typedef logging::sinks::synchronous_sink< logging::sinks::syslog_backend > sink_t;
103   boost::shared_ptr< logging::core > core = logging::core::get();
104 
105   // Create a backend
106   boost::shared_ptr< logging::sinks::syslog_backend > backend(new logging::sinks::syslog_backend(
107         keywords::facility = logging::sinks::syslog::user,
108         keywords::use_impl = logging::sinks::syslog::native,
109         keywords::ident    = log_ident
110         ));
111 
112   // Set the straightforward level translator for the "Severity" attribute of type int
113   backend->set_severity_mapper(
114       logging::sinks::syslog::direct_severity_mapping< int >("Severity"));
115 
116   // Wrap it into the frontend and register in the core.
117   // The backend requires synchronization in the frontend.
118   logging::core::get()->add_sink(boost::make_shared< sink_t >(backend));
119 }
120 
AstroidExtension(WebKitWebExtension * e,gpointer gaddr)121 AstroidExtension::AstroidExtension (
122     WebKitWebExtension * e,
123     gpointer gaddr)
124 {
125   extension = e;
126 
127   Glib::init ();
128   Gtk::Main::init_gtkmm_internals ();
129   Gio::init ();
130   logging::add_common_attributes ();
131 
132   /* load attachment icon */
133   Glib::RefPtr<Gtk::IconTheme> theme = Gtk::IconTheme::get_default();
134   attachment_icon = theme->load_icon (
135       "mail-attachment-symbolic",
136       ATTACHMENT_ICON_WIDTH,
137       Gtk::ICON_LOOKUP_USE_BUILTIN );
138 
139   /* load marked icon */
140   marked_icon = theme->load_icon (
141       "object-select-symbolic",
142       ATTACHMENT_ICON_WIDTH,
143       Gtk::ICON_LOOKUP_USE_BUILTIN );
144 
145   /* retrieve socket address */
146   gsize sz;
147   const char * caddr = g_variant_get_string ((GVariant *) gaddr, &sz);
148 
149   refptr<Gio::UnixSocketAddress> addr = Gio::UnixSocketAddress::create (caddr,
150       Gio::UNIX_SOCKET_ADDRESS_ABSTRACT);
151 
152   /* connect to socket */
153   cli = Gio::SocketClient::create ();
154 
155   try {
156     sock = cli->connect (addr);
157 
158     istream = sock->get_input_stream ();
159     ostream = sock->get_output_stream ();
160 
161     /* setting up reader thread */
162     reader_t = std::thread (&AstroidExtension::reader, this);
163 
164   } catch (Gio::Error &ex) {
165     LOG (error) << "error: " << ex.what ();
166   }
167 }
168 
~AstroidExtension()169 AstroidExtension::~AstroidExtension () {
170   /* stop reader thread */
171   run = false;
172   if (reader_cancel)
173     reader_cancel->cancel ();
174   reader_t.join ();
175 
176 
177   /* close connection */
178   sock->close ();
179 }
180 
page_created(WebKitWebExtension *,WebKitWebPage * _page,gpointer)181 void AstroidExtension::page_created (WebKitWebExtension * /* extension */,
182     WebKitWebPage * _page,
183     gpointer /* user_data */) {
184 
185   page = _page;
186   LOG (debug) << "page created.";
187 }
188 
send_request(WebKitWebPage *,WebKitURIRequest * request,WebKitURIResponse *,gpointer)189 bool AstroidExtension::send_request (
190                     WebKitWebPage    *  /* web_page */,
191                     WebKitURIRequest *  request,
192                     WebKitURIResponse * /* response */,
193                     gpointer            /* user_data */) {
194 
195   const char * curi = webkit_uri_request_get_uri (request);
196   std::string uri (curi != NULL ? curi : "");
197 
198   LOG (debug) << "request: " << uri.substr (0, std::min (60, (int)uri.size ())) << "..";
199 
200   /* allow all requests before page has been sent. no user content has been
201    * loaded yet and it seems that sometimes the request for the home uri
202    * is handled here */
203 
204   if (!page_ready || allow_remote_resources) {
205     LOG (debug) << "request: allow.";
206     return false; // allow
207   } else {
208     if (find_if (allowed_uris.begin (), allowed_uris.end (),
209           [&](std::string &a) {
210             return (uri.substr (0, a.length ()) == a);
211           }) != allowed_uris.end ())
212     {
213       LOG (debug) << "request: allow.";
214       return false; // allow
215     } else {
216       LOG (debug) << "request: blocked.";
217       return true; // stop
218     }
219   }
220 }
221 
ack(bool success)222 void AstroidExtension::ack (bool success) {
223   /* prepare and send acknowledgement message */
224   AstroidMessages::Ack m;
225   m.set_success (success);
226 
227   /* send back focus */
228   m.mutable_focus ()->set_mid (focused_message);
229   m.mutable_focus ()->set_element (focused_element);
230   m.mutable_focus ()->set_focus (true);
231 
232   AeProtocol::send_message_async (AeProtocol::MessageTypes::Ack, m, ostream, m_ostream);
233 }
234 
reader()235 void AstroidExtension::reader () {/*{{{*/
236   LOG (debug) << "reader thread: started.";
237 
238   while (run) {
239     LOG (debug) << "reader waiting..";
240 
241     std::vector<gchar> buffer;
242     AeProtocol::MessageTypes mt;
243 
244     try {
245 
246       mt = AeProtocol::read_message (
247           istream,
248           reader_cancel,
249           buffer);
250 
251     } catch (AeProtocol::ipc_error &e) {
252       LOG (warn) << "reader thread: " << e.what ();
253       run = false;
254       break;
255     }
256 
257     /* parse message */
258     switch (mt) {
259       case AeProtocol::MessageTypes::Debug:
260         {
261           AstroidMessages::Debug m;
262           m.ParseFromArray (buffer.data(), buffer.size());
263           LOG (debug) << m.msg ();
264           ack (true);
265         }
266         break;
267 
268       case AeProtocol::MessageTypes::Mark:
269         {
270           AstroidMessages::Mark m;
271           m.ParseFromArray (buffer.data(), buffer.size());
272           Glib::signal_idle().connect_once (
273               sigc::bind (
274                 sigc::mem_fun(*this, &AstroidExtension::handle_mark), m));
275         }
276         break;
277 
278       case AeProtocol::MessageTypes::Hidden:
279         {
280           AstroidMessages::Hidden m;
281           m.ParseFromArray (buffer.data(), buffer.size());
282           Glib::signal_idle().connect_once (
283               [this,m] () {
284                 set_hidden (m.mid (), m.hidden ());
285                 ack (true);
286               });
287         }
288         break;
289 
290       case AeProtocol::MessageTypes::Focus:
291         {
292           AstroidMessages::Focus m;
293           m.ParseFromArray (buffer.data(), buffer.size());
294           Glib::signal_idle().connect_once (
295               sigc::bind (
296                 sigc::mem_fun(*this, &AstroidExtension::handle_focus), m));
297         }
298         break;
299 
300       case AeProtocol::MessageTypes::State:
301         {
302           AstroidMessages::State m;
303           m.ParseFromArray (buffer.data(), buffer.size());
304           Glib::signal_idle().connect_once (
305               sigc::bind (
306                 sigc::mem_fun(*this, &AstroidExtension::handle_state), m));
307         }
308         break;
309 
310       case AeProtocol::MessageTypes::Indent:
311         {
312           AstroidMessages::Indent m;
313           m.ParseFromArray (buffer.data(), buffer.size());
314           Glib::signal_idle().connect_once (
315               [this,m] () {
316                 set_indent (m.indent ());
317                 ack (true);
318               });
319         }
320         break;
321 
322       case AeProtocol::MessageTypes::AllowRemoteImages:
323         {
324           AstroidMessages::AllowRemoteImages m;
325           m.ParseFromArray (buffer.data(), buffer.size());
326           Glib::signal_idle().connect_once (
327               [this,m] () {
328                 allow_remote_resources = true;
329                 reload_images ();
330                 ack (true);
331               });
332         }
333         break;
334 
335       case AeProtocol::MessageTypes::Page:
336         {
337           AstroidMessages::Page s;
338           s.ParseFromArray (buffer.data(), buffer.size());
339           Glib::signal_idle().connect_once (
340               sigc::bind (
341                 sigc::mem_fun(*this, &AstroidExtension::handle_page), s));
342         }
343         break;
344 
345       case AeProtocol::MessageTypes::ClearMessages:
346         {
347           AstroidMessages::ClearMessage m;
348           m.ParseFromArray (buffer.data(), buffer.size());
349           Glib::signal_idle().connect_once (
350               sigc::bind (
351                 sigc::mem_fun(*this, &AstroidExtension::clear_messages), m));
352         }
353         break;
354 
355       case AeProtocol::MessageTypes::AddMessage:
356         {
357           AstroidMessages::Message m;
358           m.ParseFromArray (buffer.data(), buffer.size());
359           Glib::signal_idle().connect_once (
360               sigc::bind (
361                 sigc::mem_fun(*this, &AstroidExtension::add_message), m));
362         }
363         break;
364 
365       case AeProtocol::MessageTypes::UpdateMessage:
366         {
367           AstroidMessages::UpdateMessage m;
368           m.ParseFromArray (buffer.data(), buffer.size());
369           Glib::signal_idle().connect_once (
370               sigc::bind (
371                 sigc::mem_fun(*this, &AstroidExtension::update_message), m));
372         }
373         break;
374 
375       case AeProtocol::MessageTypes::RemoveMessage:
376         {
377           AstroidMessages::Message m;
378           m.ParseFromArray (buffer.data(), buffer.size());
379           Glib::signal_idle().connect_once (
380               sigc::bind (
381                 sigc::mem_fun(*this, &AstroidExtension::remove_message), m));
382         }
383         break;
384 
385       case AeProtocol::MessageTypes::Info:
386         {
387           AstroidMessages::Info m;
388           m.ParseFromArray (buffer.data(), buffer.size());
389 
390           if (m.warning ()) {
391             Glib::signal_idle().connect_once (
392                 sigc::bind (
393                   sigc::mem_fun(*this, &AstroidExtension::set_warning), m));
394           } else {
395             Glib::signal_idle().connect_once (
396                 sigc::bind (
397                   sigc::mem_fun(*this, &AstroidExtension::set_info), m));
398           }
399         }
400         break;
401 
402       case AeProtocol::MessageTypes::Navigate:
403         {
404           AstroidMessages::Navigate m;
405           m.ParseFromArray (buffer.data(), buffer.size());
406           Glib::signal_idle().connect_once (
407               sigc::bind (
408                 sigc::mem_fun(*this, &AstroidExtension::handle_navigate), m));
409         }
410         break;
411 
412       default:
413         run = false;
414         break; // unknown message
415     }
416   }
417 
418   LOG (debug) << "reader thread exit.";
419 }/*}}}*/
420 
handle_page(AstroidMessages::Page & s)421 void AstroidExtension::handle_page (AstroidMessages::Page &s) {/*{{{*/
422   /* set up logging */
423   if (s.use_stdout ()) {
424     init_console_log ();
425   }
426 
427   if (s.use_syslog ()) {
428     init_sys_log ();
429   }
430 
431   if (s.disable_log ()) {
432     logging::core::get()->set_logging_enabled (false);
433   }
434 
435   logging::core::get()->set_filter (logging::trivial::severity >= sevmap[s.log_level ()]);
436 
437   GError *err = NULL;
438   WebKitDOMDocument *d = webkit_web_page_get_dom_document (page);
439 
440   /* load html */
441   LOG (debug) << "loading html..";
442 
443   WebKitDOMElement * he = webkit_dom_document_create_element (d, "HTML", (err = NULL, &err));
444   webkit_dom_element_set_outer_html (he, s.html ().c_str (), (err = NULL, &err));
445 
446   webkit_dom_document_set_body (d, WEBKIT_DOM_HTML_ELEMENT(he), (err = NULL, &err));
447 
448   /* load css style */
449   LOG (debug) << "loading stylesheet..";
450   WebKitDOMElement  *e = webkit_dom_document_create_element (d, "STYLE", (err = NULL, &err));
451 
452   WebKitDOMText *t = webkit_dom_document_create_text_node
453     (d, s.css().c_str());
454 
455   webkit_dom_node_append_child (WEBKIT_DOM_NODE(e), WEBKIT_DOM_NODE(t), (err = NULL, &err));
456 
457   WebKitDOMHTMLHeadElement * head = webkit_dom_document_get_head (d);
458   webkit_dom_node_append_child (WEBKIT_DOM_NODE(head), WEBKIT_DOM_NODE(e), (err = NULL, &err));
459   LOG (debug) << "done";
460 
461   /* store part / iframe css for later */
462   part_css = s.part_css ();
463 
464   /* store allowed uris */
465   for (auto &s : s.allowed_uris ()) {
466     allowed_uris.push_back (s);
467   }
468 
469   page_ready = true;
470 
471   g_object_unref (he);
472   g_object_unref (head);
473   g_object_unref (t);
474   g_object_unref (e);
475   g_object_unref (d);
476 
477   ack (true);
478 }/*}}}*/
479 
reload_images()480 void AstroidExtension::reload_images () {
481   LOG (debug) << "reload images.";
482   GError * err = NULL;
483   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
484 
485   for (auto &m : state.messages()) {
486     ustring div_id = "message_" + m.mid ();
487     WebKitDOMElement * me = webkit_dom_document_get_element_by_id (d, div_id.c_str());
488 
489     for (auto &c : m.elements()) {
490       if (!c.focusable ()) {
491         WebKitDOMHTMLElement * body_container = WEBKIT_DOM_HTML_ELEMENT(webkit_dom_document_get_element_by_id (d, c.sid ().c_str ()));
492         WebKitDOMHTMLElement * iframe = DomUtils::select (WEBKIT_DOM_NODE(body_container), ".body_iframe");
493         WebKitDOMDocument * iframe_d = webkit_dom_html_iframe_element_get_content_document (WEBKIT_DOM_HTML_IFRAME_ELEMENT(iframe));
494         WebKitDOMHTMLElement * b = webkit_dom_document_get_body (iframe_d);
495 
496         WebKitDOMNodeList * imgs = webkit_dom_element_query_selector_all (WEBKIT_DOM_ELEMENT(b), "img", (err = NULL, &err));
497 
498         gulong l = webkit_dom_node_list_get_length (imgs);
499         for (gulong i = 0; i < l; i++) {
500 
501           WebKitDOMNode * in = webkit_dom_node_list_item (imgs, i);
502           WebKitDOMElement * ine = WEBKIT_DOM_ELEMENT (in);
503 
504           if (ine != NULL) {
505             gchar * src = webkit_dom_element_get_attribute (ine, "src");
506             if (src != NULL) {
507               ustring usrc (src);
508               /* replace CID images with real image */
509               if (usrc.substr (0, 4) == "cid:") {
510                 ustring cid = usrc.substr (4, std::string::npos);
511                 LOG (debug) << "CID: " << cid;
512 
513                 auto s = std::find_if ( messages[m.mid()].attachments().begin (),
514                                         messages[m.mid()].attachments().end (),
515                                         [&] (auto &a) { return a.cid() == cid; } );
516 
517                 if (s != messages[m.mid()].attachments().end ()) {
518                   LOG (debug) << "found matching attachment for CID.";
519 
520                   webkit_dom_element_set_attribute (ine, "src", "", (err = NULL, &err));
521                   webkit_dom_element_set_attribute (ine, "src", s->content().c_str (), (err = NULL, &err));
522 
523                 } else {
524                   LOG (warn) << "could not find matching attachment for CID.";
525                 }
526               } else {
527 
528                 /* trigger reload */
529                 webkit_dom_element_set_attribute (ine, "src", "", (err = NULL, &err));
530                 webkit_dom_element_set_attribute (ine, "src", src, (err = NULL, &err));
531               }
532             }
533           }
534 
535           g_object_unref (in);
536         }
537 
538         g_object_unref (imgs);
539         g_object_unref (b);
540         g_object_unref (iframe_d);
541         g_object_unref (iframe);
542         g_object_unref (body_container);
543       }
544     }
545     g_object_unref (me);
546   }
547 
548   g_object_unref (d);
549 }
550 
handle_state(AstroidMessages::State & s)551 void AstroidExtension::handle_state (AstroidMessages::State &s) {/*{{{*/
552   LOG (debug) << "got state.";
553   state = s;
554   edit_mode = state.edit_mode ();
555   ack (true);
556 }/*}}}*/
557 
set_indent(bool indent)558 void AstroidExtension::set_indent (bool indent) {
559   LOG (debug) << "update indent.";
560   indent_messages = indent;
561 
562   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
563 
564   for (auto &m : state.messages()) {
565     ustring mid = "message_" + m.mid ();
566 
567     GError * err = NULL;
568 
569     WebKitDOMElement * e = webkit_dom_document_get_element_by_id (d, mid.c_str());
570 
571     /* set indentation based on level */
572     if (indent_messages && m.level() > 0) {
573       webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (e),
574           "style", ustring::compose ("margin-left: %1px", int(m.level() * INDENT_PX)).c_str(), (err = NULL, &err));
575     } else {
576       webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (e), "style");
577     }
578 
579     g_object_unref (e);
580   }
581 
582   g_object_unref (d);
583 }
584 
clear_messages(AstroidMessages::ClearMessage &)585 void AstroidExtension::clear_messages (AstroidMessages::ClearMessage &) {
586   LOG (debug) << "clearing all messages.";
587 
588   WebKitDOMDocument *d = webkit_web_page_get_dom_document (page);
589   WebKitDOMElement * container = DomUtils::get_by_id (d, "message_container");
590 
591   GError *err = NULL;
592 
593   webkit_dom_element_set_inner_html (container, "<span id=\"placeholder\"></span>", (err = NULL, &err));
594 
595   g_object_unref (container);
596   g_object_unref (d);
597 
598   /* reset */
599   focused_message = "";
600   focused_element = -1;
601   messages.clear ();
602   state = AstroidMessages::State();
603   allow_remote_resources = false;
604   indent_messages = false;
605 
606   ack (true);
607 }
608 
609 // Message generation {{{
add_message(AstroidMessages::Message & m)610 void AstroidExtension::add_message (AstroidMessages::Message &m) {
611   LOG (debug) << "adding message: " << m.mid ();
612   messages[m.mid()] = m;
613 
614   WebKitDOMDocument *d = webkit_web_page_get_dom_document (page);
615   WebKitDOMElement * container = DomUtils::get_by_id (d, "message_container");
616 
617   ustring div_id = "message_" + m.mid();
618 
619   WebKitDOMNode * insert_before = webkit_dom_node_get_last_child (
620       WEBKIT_DOM_NODE(container));
621 
622   WebKitDOMHTMLElement * div_message = DomUtils::make_message_div (d);
623 
624   GError * err = NULL;
625   webkit_dom_element_set_id (WEBKIT_DOM_ELEMENT (div_message), div_id.c_str());
626 
627   /* insert message div */
628   webkit_dom_node_insert_before (WEBKIT_DOM_NODE(container),
629       WEBKIT_DOM_NODE(div_message),
630       insert_before,
631       (err = NULL, &err));
632 
633   set_message_html (m, div_message);
634 
635   /* insert mime messages */
636   if (!m.missing_content()) {
637     insert_mime_messages (m, div_message);
638   }
639 
640   /* insert attachments */
641   if (!m.missing_content()) {
642     insert_attachments (m, div_message);
643   }
644 
645   /* marked */
646   load_marked_icon (div_message);
647 
648 
649   g_object_unref (insert_before);
650   g_object_unref (div_message);
651   g_object_unref (container);
652   g_object_unref (d);
653 
654   LOG (debug) << "message added.";
655 
656   apply_focus (focused_message, focused_element); // in case we got focus before message was added.
657 
658   ack (true);
659 }
660 
remove_message(AstroidMessages::Message & m)661 void AstroidExtension::remove_message (AstroidMessages::Message &m) {
662   LOG (debug) << "removing message: " << m.mid ();
663   messages.erase (m.mid());
664 
665   WebKitDOMDocument *d = webkit_web_page_get_dom_document (page);
666   WebKitDOMElement * container = DomUtils::get_by_id (d, "message_container");
667 
668   ustring div_id = "message_" + m.mid();
669   WebKitDOMHTMLElement * div_message = WEBKIT_DOM_HTML_ELEMENT(webkit_dom_document_get_element_by_id (d, div_id.c_str()));
670 
671   GError * err = NULL;
672   webkit_dom_node_remove_child (WEBKIT_DOM_NODE(container), WEBKIT_DOM_NODE (div_message), (err = NULL, &err));
673 
674   g_object_unref (div_message);
675   g_object_unref (container);
676   g_object_unref (d);
677 
678   LOG (debug) << "message removed.";
679 
680   ack (true);
681 }
682 
update_message(AstroidMessages::UpdateMessage & um)683 void AstroidExtension::update_message (AstroidMessages::UpdateMessage &um) {
684   auto m = um.m();
685   messages[m.mid()] = m;
686 
687   WebKitDOMDocument *d = webkit_web_page_get_dom_document (page);
688   WebKitDOMElement * container = DomUtils::get_by_id (d, "message_container");
689 
690   ustring div_id = "message_" + m.mid();
691 
692   WebKitDOMHTMLElement * old_div_message = WEBKIT_DOM_HTML_ELEMENT(webkit_dom_document_get_element_by_id (d, div_id.c_str()));
693 
694   if (um.type () == AstroidMessages::UpdateMessage_Type_VisibleParts) {
695     LOG (debug) << "updating message: " << m.mid () << " (full update)";
696     /* various states */
697     bool hidden = is_hidden (m.mid ());
698     // TODO: info and warning
699 
700     GError * err = NULL;
701 
702     WebKitDOMHTMLElement * div_message = DomUtils::make_message_div (d);
703     webkit_dom_element_set_id (WEBKIT_DOM_ELEMENT (div_message), div_id.c_str());
704     set_message_html (m, div_message);
705 
706     /* insert mime messages */
707     if (!m.missing_content()) {
708       insert_mime_messages (m, div_message);
709     }
710 
711     /* insert attachments */
712     if (!m.missing_content()) {
713       insert_attachments (m, div_message);
714     }
715 
716     /* marked */
717     load_marked_icon (div_message);
718 
719     webkit_dom_node_replace_child (WEBKIT_DOM_NODE(container), WEBKIT_DOM_NODE (div_message), WEBKIT_DOM_NODE (old_div_message), (err = NULL, &err));
720 
721     /* set various state */
722     set_hidden (m.mid (), hidden);
723     set_indent (indent_messages);
724 
725     g_object_unref (div_message);
726 
727     auto ms = std::find_if (
728         state.messages().begin(),
729         state.messages().end(),
730         [&] (auto m) {
731           return m.mid() == focused_message;
732         });
733 
734     if (!ms->elements(focused_element).focusable()) {
735       /* find next or previous element */
736 
737       /* are there any more focusable elements */
738       auto next_e = std::find_if (
739           ms->elements().begin () + (focused_element +1),
740           ms->elements().end (),
741           [&] (auto &e) { return e.focusable (); });
742 
743       if (next_e != ms->elements().end()) {
744         focused_element = std::distance (ms->elements ().begin (), next_e);
745 
746       } else {
747         LOG (debug) << "take previous";
748         /* take previous element */
749         auto next_e = std::find_if (
750             ms->elements().rbegin() +
751               (ms->elements().size() - focused_element),
752 
753             ms->elements().rend (),
754             [&] (auto &e) { return e.focusable (); });
755 
756         if (next_e != ms->elements().rend ()) {
757           /* previous */
758           focused_element = std::distance (ms->elements ().begin (), next_e.base() -1);
759         } else {
760           /* message */
761           focused_element = 0;
762         }
763       }
764 
765     }
766 
767     apply_focus (focused_message, focused_element);
768 
769   } else if (um.type () == AstroidMessages::UpdateMessage_Type_Tags) {
770     LOG (debug) << "updating message: " << m.mid () << " (tags only)";
771     message_render_tags (m, WEBKIT_DOM_HTML_ELEMENT(old_div_message));
772     message_update_css_tags (m, WEBKIT_DOM_HTML_ELEMENT(old_div_message));
773   }
774 
775   g_object_unref (old_div_message);
776   g_object_unref (container);
777   g_object_unref (d);
778 
779   ack (true);
780 }
781 
782 /* main message generation  */
set_message_html(AstroidMessages::Message m,WebKitDOMHTMLElement * div_message)783 void AstroidExtension::set_message_html (
784     AstroidMessages::Message m,
785     WebKitDOMHTMLElement * div_message)
786 {
787   GError *err;
788 
789   /* load message into div */
790   ustring header;
791   WebKitDOMHTMLElement * div_email_container =
792     DomUtils::select (WEBKIT_DOM_NODE(div_message),  ".email_container");
793 
794   /* build header */
795   insert_header_address (header, "From", m.sender(), true);
796 
797   if (m.reply_to().email().size () > 0) {
798     if (m.reply_to().email() != m.sender().email())
799       insert_header_address (header, "Reply-To", m.reply_to(), false);
800   }
801 
802   insert_header_address_list (header, "To", m.to(), false);
803 
804   if (m.cc().addresses().size () > 0) {
805     insert_header_address_list (header, "Cc", m.cc(), false);
806   }
807 
808   if (m.bcc().addresses().size () > 0) {
809     insert_header_address_list (header, "Bcc", m.bcc(), false);
810   }
811 
812   insert_header_date (header, m);
813 
814   if (m.subject().size() > 0) {
815     insert_header_row (header, "Subject", m.subject(), false);
816 
817     WebKitDOMHTMLElement * subject = DomUtils::select (
818         WEBKIT_DOM_NODE (div_message),
819         ".header_container .subject");
820 
821     ustring s = Glib::Markup::escape_text(m.subject());
822     if (static_cast<int>(s.size()) > MAX_PREVIEW_LEN)
823       s = s.substr(0, MAX_PREVIEW_LEN - 3) + "...";
824 
825     webkit_dom_element_set_inner_html (WEBKIT_DOM_ELEMENT (subject), s.c_str(), (err = NULL, &err));
826 
827     g_object_unref (subject);
828   }
829 
830   /* avatar */
831   if (!m.gravatar().empty ()) {
832     WebKitDOMHTMLImageElement * av = WEBKIT_DOM_HTML_IMAGE_ELEMENT (
833         DomUtils::select (
834         WEBKIT_DOM_NODE (div_message),
835         ".avatar" ));
836 
837     webkit_dom_html_image_element_set_src (av, m.gravatar().c_str());
838 
839     g_object_unref (av);
840   }
841 
842   /* insert header html*/
843   WebKitDOMHTMLElement * table_header =
844     DomUtils::select (WEBKIT_DOM_NODE(div_email_container),
845         ".header_container .header" );
846 
847   header += create_header_row ("Tags", "", false, false, true);
848 
849   webkit_dom_element_set_inner_html (
850       WEBKIT_DOM_ELEMENT(table_header),
851       header.c_str(),
852       (err = NULL, &err));
853 
854   if (m.tags().size () > 0) {
855     message_render_tags (m, WEBKIT_DOM_HTML_ELEMENT(div_message));
856     message_update_css_tags (m, WEBKIT_DOM_HTML_ELEMENT(div_message));
857   }
858 
859   /* if message is missing body, set warning and don't add any content */
860   WebKitDOMHTMLElement * span_body =
861     DomUtils::select (WEBKIT_DOM_NODE(div_email_container),
862         ".body" );
863 
864   WebKitDOMHTMLElement * preview =
865     DomUtils::select (WEBKIT_DOM_NODE(div_email_container),
866         ".header_container .preview" );
867 
868   if (m.missing_content()) {
869     /* set preview */
870     webkit_dom_element_set_inner_html (WEBKIT_DOM_ELEMENT(preview), "<i>Message content is missing.</i>", (err = NULL, &err));
871 
872     /* set warning */
873     AstroidMessages::Info i;
874     i.set_mid (m.mid());
875     i.set_set (true);
876     i.set_txt ("The message file is missing, only fields cached in the notmuch database are shown. Most likely your database is out of sync.");
877 
878     set_warning (i);
879 
880     /* add an explanation to the body */
881     GError *err;
882 
883     WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
884     WebKitDOMHTMLElement * body_container =
885       DomUtils::clone_get_by_id (d, "body_template");
886 
887     webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (body_container),
888         "id");
889 
890     webkit_dom_element_set_inner_html (
891         WEBKIT_DOM_ELEMENT(body_container),
892         "<i>Message content is missing.</i>",
893         (err = NULL, &err));
894 
895     webkit_dom_node_append_child (WEBKIT_DOM_NODE (span_body),
896         WEBKIT_DOM_NODE (body_container), (err = NULL, &err));
897 
898     g_object_unref (body_container);
899     g_object_unref (d);
900 
901   } else {
902 
903     /* build message body */
904     create_message_part_html (m, m.root(), span_body);
905 
906     /* preview */
907     webkit_dom_element_set_inner_html (WEBKIT_DOM_ELEMENT(preview), m.preview().c_str(), (err = NULL, &err));
908   }
909 
910   g_object_unref (preview);
911   g_object_unref (span_body);
912   g_object_unref (table_header);
913 } //
914 
message_render_tags(AstroidMessages::Message & m,WebKitDOMHTMLElement * div_message)915 void AstroidExtension::message_render_tags (AstroidMessages::Message &m,
916     WebKitDOMHTMLElement * div_message) {
917 
918   GError *err;
919 
920   WebKitDOMHTMLElement * tags = DomUtils::select (
921       WEBKIT_DOM_NODE (div_message),
922       ".header_container .tags");
923 
924   webkit_dom_element_set_inner_html (WEBKIT_DOM_ELEMENT(tags), m.tag_string().c_str (), (err = NULL, &err));
925 
926   g_object_unref (tags);
927 
928   tags = DomUtils::select (
929       WEBKIT_DOM_NODE (div_message),
930       ".header_container .header div#Tags .value");
931 
932   webkit_dom_element_set_inner_html (WEBKIT_DOM_ELEMENT(tags), m.tag_string().c_str (), (err = NULL, &err));
933 
934   g_object_unref (tags);
935 }
936 
message_update_css_tags(AstroidMessages::Message & m,WebKitDOMHTMLElement * div_message)937 void AstroidExtension::message_update_css_tags (AstroidMessages::Message &m,
938     WebKitDOMHTMLElement * div_message) {
939   /* check for tag changes that control display */
940   WebKitDOMDOMTokenList * class_list =
941     webkit_dom_element_get_class_list (WEBKIT_DOM_ELEMENT(div_message));
942 
943   /* patches may be rendered somewhat differently */
944   DomUtils::switch_class (class_list, "patch", m.patch ());
945 
946   /* message subject deviates from thread subject */
947   DomUtils::switch_class (class_list, "different_subject", m.different_subject ());
948 
949   /* reset notmuch tags */
950   for (unsigned int i = 0; i < webkit_dom_dom_token_list_get_length (class_list); i++)
951   {
952     const char * _t = webkit_dom_dom_token_list_item (class_list, i);
953     ustring t (_t);
954 
955     if (t.find ("nm-", 0) != std::string::npos) {
956       DomUtils::switch_class (class_list, t, false);
957     }
958   }
959 
960   for (ustring t : m.tags()) {
961     t = UstringUtils::replace (t, "/", "-");
962     t = UstringUtils::replace (t, ".", "-");
963     t = Glib::Markup::escape_text (t);
964 
965     t = "nm-" + t;
966     DomUtils::switch_class (class_list, t, true);
967   }
968 
969   g_object_unref (class_list);
970 }
971 
create_message_part_html(const AstroidMessages::Message & message,const AstroidMessages::Message::Chunk & c,WebKitDOMHTMLElement * span_body)972 void AstroidExtension::create_message_part_html (
973     const AstroidMessages::Message &message,
974     const AstroidMessages::Message::Chunk &c,
975     WebKitDOMHTMLElement * span_body)
976 {
977 
978   ustring mime_type = c.mime_type ();
979 
980    LOG (debug) << "create message part: " << c.id() << " (siblings: " << c.sibling() << ") (kids: " << c.kids().size() << ")" <<
981     " (attachment: " << c.attachment() << ")" << " (viewable: " << c.viewable() << ")" << " (focusable: " << c.focusable () << ")" << " (mimetype: " << mime_type << ")";
982 
983   if (c.use()) {
984     if (!c.focusable () && c.viewable()) {
985       create_body_part (message, c, span_body);
986     } else if (c.viewable()) {
987       create_sibling_part (c, span_body);
988     }
989 
990     /* descend */
991     for (auto &k: c.kids()) {
992       create_message_part_html (message, k, span_body);
993     }
994   } else {
995     if (!c.focusable ()) {
996       create_body_part (message, c, span_body);
997     } else {
998       create_sibling_part (c, span_body);
999     }
1000   }
1001 }
1002 
create_body_part(const AstroidMessages::Message & message,const AstroidMessages::Message::Chunk & c,WebKitDOMHTMLElement * span_body)1003 void AstroidExtension::create_body_part (
1004     const AstroidMessages::Message &message,
1005     const AstroidMessages::Message::Chunk &c,
1006     WebKitDOMHTMLElement * span_body)
1007 {
1008   // <span id="body_template" class="body_part"></span>
1009 
1010   LOG (debug) << "create body part: " << c.id();
1011 
1012   GError *err;
1013 
1014   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1015   WebKitDOMHTMLElement * body_container =
1016     DomUtils::clone_select (WEBKIT_DOM_NODE(d), "#body_template");
1017 
1018   webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (body_container),
1019       "id");
1020 
1021   webkit_dom_element_set_id (WEBKIT_DOM_ELEMENT (body_container),
1022       c.sid().c_str ());
1023 
1024   ustring body = c.content();
1025 
1026   /* check encryption */
1027   //
1028   //  <div id="encrypt_template" class=encrypt_container">
1029   //      <div class="message"></div>
1030   //  </div>
1031   if (c.is_encrypted() || c.is_signed()) {
1032     WebKitDOMHTMLElement * encrypt_container =
1033       DomUtils::clone_select (WEBKIT_DOM_NODE(d), "#encrypt_template");
1034 
1035     webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (encrypt_container),
1036         "id");
1037 
1038     webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (encrypt_container),
1039       "id", ustring::compose ("%1", c.crypto_id()).c_str (),
1040       (err = NULL, &err));
1041 
1042     WebKitDOMDOMTokenList * class_list_e =
1043       webkit_dom_element_get_class_list (WEBKIT_DOM_ELEMENT(encrypt_container));
1044 
1045 
1046     ustring content = "";
1047 
1048     ustring sign_string = "";
1049     ustring enc_string  = "";
1050 
1051     vector<ustring> all_sig_errors;
1052 
1053     if (c.is_signed ()) {
1054 
1055       if (c.signature().verified()) {
1056         sign_string += "<span class=\"header\">Signature verification succeeded.</span>";
1057       } else {
1058         sign_string += "<span class=\"header\">Signature verification failed!</span>";
1059       }
1060 
1061       for (auto &s : c.signature().sign_strings ()) {
1062         sign_string += s;
1063       }
1064     }
1065 
1066     if (c.is_encrypted ()) {
1067       if (c.is_signed ()) enc_string = "<span class=\"header\">Signed and Encrypted.</span>";
1068       else                enc_string = "<span class=\"header\">Encrypted.</span>";
1069 
1070 
1071       if (c.encryption().decrypted ()) {
1072         for (auto &e : c.encryption ().enc_strings ()) {
1073           enc_string += e;
1074         }
1075 
1076         if (c.is_signed ()) enc_string += "<br /><br />";
1077 
1078       } else {
1079         enc_string += "Encryption: Failed decryption.";
1080 
1081       }
1082     }
1083 
1084     content = enc_string + sign_string;
1085 
1086     WebKitDOMHTMLElement * message_cont =
1087       DomUtils::select (WEBKIT_DOM_NODE (encrypt_container), ".message");
1088 
1089     webkit_dom_element_set_inner_html (
1090         WEBKIT_DOM_ELEMENT(message_cont),
1091         content.c_str(),
1092         (err = NULL, &err));
1093 
1094 
1095     webkit_dom_node_append_child (WEBKIT_DOM_NODE (span_body),
1096         WEBKIT_DOM_NODE (encrypt_container), (err = NULL, &err));
1097 
1098     /* add encryption tag to encrypted part */
1099     WebKitDOMDOMTokenList * class_list =
1100       webkit_dom_element_get_class_list (WEBKIT_DOM_ELEMENT(body_container));
1101 
1102     if (c.is_encrypted ()) {
1103       DomUtils::switch_class (class_list_e, "encrypted", true);
1104       DomUtils::switch_class (class_list, "encrypted", true);
1105 
1106       if (!c.encryption().decrypted()) {
1107         DomUtils::switch_class (class_list_e, "decrypt_failed", true);
1108         DomUtils::switch_class (class_list, "decrypt_failed", true);
1109       }
1110     }
1111 
1112     if (c.is_signed ()) {
1113       DomUtils::switch_class (class_list_e, "signed", true);
1114       DomUtils::switch_class (class_list, "signed", true);
1115 
1116       if (!c.signature().verified()) {
1117         DomUtils::switch_class (class_list_e, "verify_failed", true);
1118         DomUtils::switch_class (class_list, "verify_failed", true);
1119 
1120         /* add specific errors */
1121         std::sort (all_sig_errors.begin (), all_sig_errors.end ());
1122         all_sig_errors.erase (unique (all_sig_errors.begin (), all_sig_errors.end ()), all_sig_errors.end ());
1123 
1124         for (ustring & e : all_sig_errors) {
1125           DomUtils::switch_class (class_list_e, e, true);
1126           DomUtils::switch_class (class_list, e, true);
1127         }
1128       }
1129     }
1130 
1131     g_object_unref (class_list);
1132     g_object_unref (class_list_e);
1133     g_object_unref (encrypt_container);
1134     g_object_unref (message_cont);
1135 
1136   }
1137 
1138   webkit_dom_node_append_child (WEBKIT_DOM_NODE (span_body),
1139       WEBKIT_DOM_NODE (body_container), (err = NULL, &err));
1140 
1141   g_object_unref (d);
1142 
1143   /*
1144    * run this later on extension GUI thread in order to make sure that the "body part" has been
1145    * added to the document.
1146    */
1147   Glib::signal_idle().connect_once (
1148       sigc::bind (
1149         sigc::mem_fun(*this, &AstroidExtension::set_iframe_src), message.mid(), c.sid(), body));
1150 
1151   LOG (debug) << "create_body_part done.";
1152 }
1153 
set_iframe_src(ustring mid,ustring cid,ustring body)1154 void AstroidExtension::set_iframe_src (ustring mid, ustring cid, ustring body) {
1155   LOG (debug) << "set iframe src: " << mid << ", " << cid;
1156 
1157   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1158   GError *err;
1159 
1160   WebKitDOMHTMLElement * body_container = WEBKIT_DOM_HTML_ELEMENT(webkit_dom_document_get_element_by_id (d, cid.c_str ()));
1161 
1162   WebKitDOMHTMLElement * iframe =
1163     DomUtils::select (WEBKIT_DOM_NODE(body_container), ".body_iframe");
1164 
1165 
1166   /* by using srcdoc we avoid creating any requests that would have to be
1167    * allowed on the main GUI thread. even if we run this function async there
1168    * might be other sync calls to the webextension that cause blocking since
1169    * most webextension funcs need to run on extension GUI thread in order to
1170    * manipulate DOM tree */
1171 
1172   /* according to: http://w3c.github.io/html/semantics-embedded-content.html#element-attrdef-iframe-src
1173    * we need to escape quotation marks and amperands. it seems that by using
1174    * this call webkit does this for us. this is critical since otherwise the
1175    * content could break out of the iframe. */
1176 
1177   /* it would probably be possible to mess up the style, but it should only affect the current frame content. this would anyway be possible. */
1178 
1179   webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (iframe), "srcdoc",
1180       ustring::compose (
1181         "<STYLE>%1</STYLE>%2",
1182         part_css,
1183         body ).c_str (),
1184       (err = NULL, &err));
1185 
1186   g_object_unref (iframe);
1187   g_object_unref (body_container);
1188   g_object_unref (d);
1189 }
1190 
create_sibling_part(const AstroidMessages::Message::Chunk & sibling,WebKitDOMHTMLElement * span_body)1191 void AstroidExtension::create_sibling_part (
1192     /* const AstroidMessages::Message &message, */
1193     const AstroidMessages::Message::Chunk &sibling,
1194     WebKitDOMHTMLElement * span_body) {
1195 
1196   LOG (debug) << "create sibling part: " << sibling.id ();
1197   //
1198   //  <div id="sibling_template" class=sibling_container">
1199   //      <div class="message"></div>
1200   //  </div>
1201 
1202   GError *err;
1203 
1204   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1205   WebKitDOMHTMLElement * sibling_container =
1206     DomUtils::clone_select (WEBKIT_DOM_NODE(d), "#sibling_template");
1207 
1208   webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (sibling_container),
1209       "id");
1210 
1211   webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (sibling_container),
1212     "id", sibling.sid().c_str(),
1213     (err = NULL, &err));
1214 
1215   ustring content = ustring::compose ("Alternative part (type: %1)%2",
1216       Glib::Markup::escape_text(sibling.mime_type ()),
1217       (sibling.mime_type() != "text/plain" ? " - potentially sketchy." : ""));
1218 
1219   WebKitDOMHTMLElement * message_cont =
1220     DomUtils::select (WEBKIT_DOM_NODE (sibling_container), ".message");
1221 
1222   webkit_dom_element_set_inner_html (
1223       WEBKIT_DOM_ELEMENT(message_cont),
1224       content.c_str(),
1225       (err = NULL, &err));
1226 
1227   webkit_dom_node_append_child (WEBKIT_DOM_NODE (span_body),
1228       WEBKIT_DOM_NODE (sibling_container), (err = NULL, &err));
1229 
1230   g_object_unref (message_cont);
1231   g_object_unref (sibling_container);
1232   g_object_unref (d);
1233 } //
1234 
insert_mime_messages(AstroidMessages::Message & m,WebKitDOMHTMLElement * div_message)1235 void AstroidExtension::insert_mime_messages (
1236     AstroidMessages::Message &m,
1237     WebKitDOMHTMLElement * div_message)
1238 {
1239   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1240   WebKitDOMHTMLElement * div_email_container =
1241     DomUtils::select (WEBKIT_DOM_NODE(div_message), "div.email_container");
1242 
1243   WebKitDOMHTMLElement * span_body =
1244     DomUtils::select (WEBKIT_DOM_NODE(div_email_container), ".body");
1245 
1246   for (auto &c : m.mime_messages ()) {
1247     LOG (debug) << "create mime message part: " << c.id();
1248     //
1249     //  <div id="mime_template" class=mime_container">
1250     //      <div class="top_border"></div>
1251     //      <div class="message"></div>
1252     //  </div>
1253 
1254     GError *err;
1255 
1256     WebKitDOMHTMLElement * mime_container =
1257       DomUtils::clone_select (WEBKIT_DOM_NODE(d), "#mime_template");
1258 
1259     webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (mime_container),
1260         "id");
1261 
1262     webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (mime_container),
1263       "id", c.sid().c_str(),
1264       (err = NULL, &err));
1265 
1266     ustring content = ustring::compose ("MIME message (subject: %1, size: %2 B) - potentially sketchy.",
1267         Glib::Markup::escape_text(c.filename ()),
1268         c.human_size (),
1269         c.sid ());
1270 
1271     WebKitDOMHTMLElement * message_cont =
1272       DomUtils::select (WEBKIT_DOM_NODE (mime_container), ".message");
1273 
1274     webkit_dom_element_set_inner_html (
1275         WEBKIT_DOM_ELEMENT(message_cont),
1276         content.c_str(),
1277         (err = NULL, &err));
1278 
1279 
1280     webkit_dom_node_append_child (WEBKIT_DOM_NODE (span_body),
1281         WEBKIT_DOM_NODE (mime_container), (err = NULL, &err));
1282 
1283     g_object_unref (message_cont);
1284     g_object_unref (mime_container);
1285 
1286   }
1287 
1288   g_object_unref (span_body);
1289   g_object_unref (div_email_container);
1290   g_object_unref (d);
1291 }
1292 
insert_attachments(AstroidMessages::Message & m,WebKitDOMHTMLElement * div_message)1293 void AstroidExtension::insert_attachments (
1294     AstroidMessages::Message &m,
1295     WebKitDOMHTMLElement * div_message)
1296 {
1297   // <div class="attachment_container">
1298   //     <div class="top_border"></div>
1299   //     <table class="attachment" data-attachment-id="">
1300   //         <tr>
1301   //             <td class="preview">
1302   //                 <img src="" />
1303   //             </td>
1304   //             <td class="info">
1305   //                 <div class="filename"></div>
1306   //                 <div class="filesize"></div>
1307   //             </td>
1308   //         </tr>
1309   //     </table>
1310   // </div>
1311 
1312   GError *err;
1313 
1314   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1315   WebKitDOMHTMLElement * attachment_container =
1316     DomUtils::clone_select (WEBKIT_DOM_NODE(d), "#attachment_template");
1317   WebKitDOMHTMLElement * attachment_template =
1318     DomUtils::select (WEBKIT_DOM_NODE(attachment_container), ".attachment");
1319 
1320   webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (attachment_container),
1321       "id");
1322   webkit_dom_node_remove_child (WEBKIT_DOM_NODE (attachment_container),
1323       WEBKIT_DOM_NODE(attachment_template), (err = NULL, &err));
1324 
1325   int attachments = 0;
1326 
1327   /* generate an attachment table for each attachment */
1328   for (auto &c : m.attachments ()) {
1329     WebKitDOMHTMLElement * attachment_table =
1330       DomUtils::clone_node (WEBKIT_DOM_NODE (attachment_template));
1331 
1332     attachments++;
1333 
1334     WebKitDOMHTMLElement * info_fname =
1335       DomUtils::select (WEBKIT_DOM_NODE (attachment_table), ".info .filename");
1336 
1337     ustring fname = c.filename ();
1338     if (fname.size () == 0) {
1339       fname = "Unnamed attachment";
1340     }
1341 
1342     fname = Glib::Markup::escape_text (fname);
1343 
1344     webkit_dom_html_element_set_inner_text (info_fname, fname.c_str(), (err = NULL, &err));
1345 
1346     WebKitDOMHTMLElement * info_fsize =
1347       DomUtils::select (WEBKIT_DOM_NODE (attachment_table), ".info .filesize");
1348 
1349     webkit_dom_html_element_set_inner_text (info_fsize, c.human_size().c_str(), (err = NULL, &err));
1350 
1351 
1352     webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (attachment_table),
1353       "data-attachment-id", c.sid().c_str(),
1354       (err = NULL, &err));
1355     webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (attachment_table),
1356       "id", c.sid().c_str(),
1357       (err = NULL, &err));
1358 
1359     // set image
1360     WebKitDOMHTMLImageElement * img =
1361       WEBKIT_DOM_HTML_IMAGE_ELEMENT(
1362       DomUtils::select (WEBKIT_DOM_NODE (attachment_table), ".preview img"));
1363 
1364     webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (img), "src",
1365         c.thumbnail().c_str(), &err);
1366 
1367     // add the attachment table
1368     webkit_dom_node_append_child (WEBKIT_DOM_NODE (attachment_container),
1369         WEBKIT_DOM_NODE (attachment_table), (err = NULL, &err));
1370 
1371 
1372     if (c.is_signed () || c.is_encrypted ()) {
1373       /* add encryption or signed tag to attachment */
1374       WebKitDOMDOMTokenList * class_list =
1375         webkit_dom_element_get_class_list (WEBKIT_DOM_ELEMENT(attachment_table));
1376 
1377       if (c.is_encrypted ()) {
1378         DomUtils::switch_class (class_list, "encrypted", true);
1379       }
1380 
1381       if (c.is_signed ()) {
1382         DomUtils::switch_class (class_list, "signed", true);
1383       }
1384 
1385       g_object_unref (class_list);
1386     }
1387 
1388     g_object_unref (img);
1389     g_object_unref (info_fname);
1390     g_object_unref (info_fsize);
1391     g_object_unref (attachment_table);
1392 
1393   }
1394 
1395   if (attachments > 0) {
1396     webkit_dom_node_append_child (WEBKIT_DOM_NODE (div_message),
1397         WEBKIT_DOM_NODE (attachment_container), (err = NULL, &err));
1398   }
1399 
1400   g_object_unref (attachment_template);
1401   g_object_unref (attachment_container);
1402   g_object_unref (d);
1403 
1404 
1405   if (attachments > 0)
1406     set_attachment_icon (div_message);
1407 }
1408 
set_attachment_icon(WebKitDOMHTMLElement * div_message)1409 void AstroidExtension::set_attachment_icon (
1410     WebKitDOMHTMLElement * div_message)
1411 {
1412   GError *err;
1413 
1414   WebKitDOMHTMLElement * attachment_icon_img = DomUtils::select (
1415       WEBKIT_DOM_NODE (div_message),
1416       ".attachment.icon.first");
1417 
1418   gchar * content;
1419   gsize   content_size;
1420   attachment_icon->save_to_buffer (content, content_size, "png");
1421   ustring image_content_type = "image/png";
1422 
1423   WebKitDOMHTMLImageElement *img = WEBKIT_DOM_HTML_IMAGE_ELEMENT (attachment_icon_img);
1424 
1425   err = NULL;
1426   webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (img), "src",
1427       DomUtils::assemble_data_uri (image_content_type.c_str (), content, content_size).c_str(), &err);
1428 
1429   g_object_unref (attachment_icon_img);
1430 
1431   attachment_icon_img = DomUtils::select (
1432       WEBKIT_DOM_NODE (div_message),
1433       ".attachment.icon.sec");
1434   img = WEBKIT_DOM_HTML_IMAGE_ELEMENT (attachment_icon_img);
1435 
1436   err = NULL;
1437   webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (img), "src",
1438       DomUtils::assemble_data_uri (image_content_type.c_str (), content, content_size).c_str(), &err);
1439 
1440   WebKitDOMDOMTokenList * class_list =
1441     webkit_dom_element_get_class_list (WEBKIT_DOM_ELEMENT(div_message));
1442 
1443   DomUtils::switch_class (class_list, "attachment", true);
1444 
1445   g_object_unref (class_list);
1446   g_object_unref (attachment_icon_img);
1447 }
1448 
load_marked_icon(WebKitDOMHTMLElement * div_message)1449 void AstroidExtension::load_marked_icon (WebKitDOMHTMLElement * div_message) {
1450 
1451   WebKitDOMHTMLElement * marked_icon_img = DomUtils::select (
1452       WEBKIT_DOM_NODE (div_message),
1453       ".marked.icon.first");
1454 
1455   gchar * content;
1456   gsize   content_size;
1457   marked_icon->save_to_buffer (content, content_size, "png");
1458   ustring image_content_type = "image/png";
1459 
1460   WebKitDOMHTMLImageElement *img = WEBKIT_DOM_HTML_IMAGE_ELEMENT (marked_icon_img);
1461 
1462   webkit_dom_html_image_element_set_src (img, DomUtils::assemble_data_uri (image_content_type.c_str (), content, content_size).c_str());
1463 
1464   g_object_unref (marked_icon_img);
1465 
1466   marked_icon_img = DomUtils::select (
1467       WEBKIT_DOM_NODE (div_message),
1468       ".marked.icon.sec");
1469   img = WEBKIT_DOM_HTML_IMAGE_ELEMENT (marked_icon_img);
1470 
1471   webkit_dom_html_image_element_set_src (img, DomUtils::assemble_data_uri (image_content_type.c_str (), content, content_size).c_str());
1472 
1473   g_object_unref (marked_icon_img);
1474 }
1475 
1476 /* headers  {{{ */
insert_header_date(ustring & header,AstroidMessages::Message m)1477 void AstroidExtension::insert_header_date (ustring & header, AstroidMessages::Message m)
1478 {
1479   ustring value = ustring::compose (
1480               "<span class=\"hidden_only\">%1</span>"
1481               "<span class=\"not_hidden_only\">%2</span>",
1482               m.date_pretty (),
1483               m.date_verbose ());
1484 
1485   header += create_header_row ("Date", value, true, false);
1486 }
1487 
insert_header_address(ustring & header,ustring title,AstroidMessages::Address address,bool important)1488 void AstroidExtension::insert_header_address (
1489     ustring &header,
1490     ustring title,
1491     AstroidMessages::Address address,
1492     bool important) {
1493 
1494   AstroidMessages::AddressList al;
1495   AstroidMessages::Address * a = al.add_addresses ();
1496   a->set_name (address.name());
1497   a->set_full_address (address.full_address ());
1498   a->set_email (address.email ());
1499 
1500   insert_header_address_list (header, title, al, important);
1501 }
1502 
insert_header_address_list(ustring & header,ustring title,AstroidMessages::AddressList addresses,bool important)1503 void AstroidExtension::insert_header_address_list (
1504     ustring &header,
1505     ustring title,
1506     AstroidMessages::AddressList addresses,
1507     bool important) {
1508 
1509   ustring value;
1510   bool first = true;
1511 
1512   for (const AstroidMessages::Address address : addresses.addresses()) {
1513     if (address.full_address().size() > 0) {
1514       if (!first) {
1515         value += ", ";
1516       } else {
1517         first = false;
1518       }
1519 
1520       value +=
1521         ustring::compose ("<a href=\"mailto:%3\">%4%1%5 &lt;%2&gt;</a>",
1522           Glib::Markup::escape_text (address.name ()),
1523           Glib::Markup::escape_text (address.email ()),
1524           Glib::Markup::escape_text (address.full_address()),
1525           (important ? "<b>" : ""),
1526           (important ? "</b>" : "")
1527           );
1528     }
1529   }
1530 
1531   header += create_header_row (title, value, important, false, false);
1532 }
1533 
insert_header_row(ustring & header,ustring title,ustring value,bool important)1534 void AstroidExtension::insert_header_row (
1535     ustring &header,
1536     ustring title,
1537     ustring value,
1538     bool important) {
1539 
1540   header += create_header_row (title, value, important, true, false);
1541 }
1542 
1543 
create_header_row(ustring title,ustring value,bool important,bool escape,bool noprint)1544 ustring AstroidExtension::create_header_row (
1545     ustring title,
1546     ustring value,
1547     bool important,
1548     bool escape,
1549     bool noprint) {
1550 
1551   return ustring::compose (
1552       "<div class=\"field%1%2\" id=\"%3\">"
1553       "  <div class=\"title\">%3:</div>"
1554       "  <div class=\"value\">%4</div>"
1555       "</div>",
1556       (important ? " important" : ""),
1557       (noprint ? " noprint" : ""),
1558       Glib::Markup::escape_text (title),
1559       (escape ? Glib::Markup::escape_text (value) : value)
1560       );
1561 }
1562 /* headers end  }}} */
1563 
1564 // }}}
1565 
1566 /* warning and info {{{ */
set_warning(AstroidMessages::Info & m)1567 void AstroidExtension::set_warning (AstroidMessages::Info &m) {
1568   if (!m.set ()) {
1569     hide_warning (m); // ack's
1570     return;
1571   }
1572   LOG (debug) << "set warning: " << m.txt ();
1573 
1574   ustring mid = "message_" + m.mid();
1575   ustring txt = m.txt();
1576 
1577   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1578   WebKitDOMElement * e = webkit_dom_document_get_element_by_id (d, mid.c_str());
1579 
1580   WebKitDOMHTMLElement * warning = DomUtils::select (
1581       WEBKIT_DOM_NODE (e),
1582       ".email_warning");
1583 
1584   GError * err;
1585   webkit_dom_element_set_inner_html (WEBKIT_DOM_ELEMENT(warning), txt.c_str(), (err = NULL, &err));
1586 
1587   WebKitDOMDOMTokenList * class_list =
1588     webkit_dom_element_get_class_list (WEBKIT_DOM_ELEMENT(warning));
1589 
1590   DomUtils::switch_class (class_list, "show", true);
1591 
1592   g_object_unref (class_list);
1593   g_object_unref (warning);
1594   g_object_unref (e);
1595   g_object_unref (d);
1596   ack (true);
1597 }
1598 
hide_warning(AstroidMessages::Info & m)1599 void AstroidExtension::hide_warning (AstroidMessages::Info &m)
1600 {
1601   LOG (debug) << "hide warning.";
1602   ustring mid = "message_" + m.mid();
1603 
1604   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1605   WebKitDOMElement * e = webkit_dom_document_get_element_by_id (d, mid.c_str());
1606 
1607   WebKitDOMHTMLElement * warning = DomUtils::select (
1608       WEBKIT_DOM_NODE (e),
1609       ".email_warning");
1610 
1611   GError * err;
1612   webkit_dom_element_set_inner_html (WEBKIT_DOM_ELEMENT(warning), "", (err = NULL, &err));
1613 
1614   WebKitDOMDOMTokenList * class_list =
1615     webkit_dom_element_get_class_list (WEBKIT_DOM_ELEMENT(warning));
1616 
1617   DomUtils::switch_class (class_list, "show", false);
1618 
1619   g_object_unref (class_list);
1620   g_object_unref (warning);
1621   g_object_unref (e);
1622   g_object_unref (d);
1623 
1624   ack (true);
1625 }
1626 
set_info(AstroidMessages::Info & m)1627 void AstroidExtension::set_info (AstroidMessages::Info &m)
1628 {
1629   if (!m.set ()) {
1630     hide_info (m); // ack's
1631     return;
1632   }
1633   LOG (debug) << "set info: " << m.txt ();
1634 
1635   ustring mid = "message_" + m.mid();
1636   ustring txt = m.txt();
1637 
1638   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1639   WebKitDOMElement * e = webkit_dom_document_get_element_by_id (d, mid.c_str());
1640 
1641   WebKitDOMHTMLElement * info = DomUtils::select (
1642       WEBKIT_DOM_NODE (e),
1643       ".email_info");
1644 
1645   GError * err;
1646   webkit_dom_element_set_inner_html (WEBKIT_DOM_ELEMENT(info), txt.c_str(), (err = NULL, &err));
1647 
1648   WebKitDOMDOMTokenList * class_list =
1649     webkit_dom_element_get_class_list (WEBKIT_DOM_ELEMENT(info));
1650 
1651   DomUtils::switch_class (class_list, "show", true);
1652 
1653   g_object_unref (class_list);
1654   g_object_unref (info);
1655   g_object_unref (e);
1656   g_object_unref (d);
1657 
1658   ack (true);
1659 }
1660 
hide_info(AstroidMessages::Info & m)1661 void AstroidExtension::hide_info (AstroidMessages::Info &m) {
1662   LOG (debug) << "hide info.";
1663   ustring mid = "message_" + m.mid();
1664 
1665   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1666   WebKitDOMElement * e = webkit_dom_document_get_element_by_id (d, mid.c_str());
1667 
1668   WebKitDOMHTMLElement * info = DomUtils::select (
1669       WEBKIT_DOM_NODE (e),
1670       ".email_info");
1671 
1672   GError * err;
1673   webkit_dom_element_set_inner_html (WEBKIT_DOM_ELEMENT(info), "", (err = NULL, &err));
1674 
1675   WebKitDOMDOMTokenList * class_list =
1676     webkit_dom_element_get_class_list (WEBKIT_DOM_ELEMENT(info));
1677 
1678   DomUtils::switch_class (class_list, "show", false);
1679 
1680   g_object_unref (class_list);
1681   g_object_unref (info);
1682   g_object_unref (e);
1683   g_object_unref (d);
1684 
1685   ack (true);
1686 }
1687 
1688 /* }}} */
1689 
set_hidden(ustring mid,bool hidden)1690 void AstroidExtension::set_hidden (ustring mid, bool hidden) {
1691   /* hide or show message */
1692   LOG (debug) << "set hidden";
1693   ustring div_id = "message_" + mid;
1694 
1695   GError * err = NULL;
1696 
1697   WebKitDOMDocument *d = webkit_web_page_get_dom_document (page);
1698   WebKitDOMElement * e = webkit_dom_document_get_element_by_id (d, div_id.c_str());
1699 
1700   WebKitDOMDOMTokenList * class_list =
1701     webkit_dom_element_get_class_list (WEBKIT_DOM_ELEMENT(e));
1702 
1703   if (hidden) {
1704     LOG (debug) << "hide: " << mid;
1705     webkit_dom_dom_token_list_toggle (class_list, "hide", hidden, &err );
1706   } else if (webkit_dom_dom_token_list_contains (class_list, "hide")) {
1707     LOG (debug) << "show: " << mid;
1708     webkit_dom_dom_token_list_toggle (class_list, "hide", false, &err );
1709   }
1710 
1711   /* if the message we just hid or showed is not the focused one it may have
1712    * caused the focused message to go out of view. scroll to original focused
1713    * message in that case. */
1714 
1715   if (mid != focused_message && !focused_message.empty()) {
1716     if (focused_element > 0) {
1717       scroll_to_element (
1718           std::find_if (state.messages().begin (), state.messages().end (),
1719             [&] (auto &m) { return m.mid () == focused_message; })->elements(focused_element).sid ()
1720           );
1721     } else {
1722       ustring div = "message_" + focused_message;
1723       scroll_to_element (div);
1724     }
1725   }
1726 
1727   g_object_unref (class_list);
1728   g_object_unref (e);
1729   g_object_unref (d);
1730 }
1731 
is_hidden(ustring mid)1732 bool AstroidExtension::is_hidden (ustring mid) {
1733   ustring mmid = "message_" + mid;
1734 
1735   WebKitDOMDocument *d = webkit_web_page_get_dom_document (page);
1736   WebKitDOMElement * e = webkit_dom_document_get_element_by_id (d, mmid.c_str());
1737 
1738   WebKitDOMDOMTokenList * class_list =
1739     webkit_dom_element_get_class_list (e);
1740 
1741   bool r = webkit_dom_dom_token_list_contains (class_list, "hide");
1742 
1743   g_object_unref (class_list);
1744   g_object_unref (e);
1745   g_object_unref (d);
1746 
1747   return r;
1748 }
1749 
handle_mark(AstroidMessages::Mark & m)1750 void AstroidExtension::handle_mark (AstroidMessages::Mark &m) {/*{{{*/
1751   ustring mid = "message_" + m.mid();
1752 
1753   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1754 
1755   WebKitDOMElement * e = webkit_dom_document_get_element_by_id (d, mid.c_str());
1756 
1757   WebKitDOMDOMTokenList * class_list =
1758     webkit_dom_element_get_class_list (e);
1759 
1760   /* set class  */
1761   DomUtils::switch_class (class_list, "marked", m.marked());
1762 
1763   g_object_unref (class_list);
1764   g_object_unref (e);
1765   g_object_unref (d);
1766 
1767   ack (true);
1768 }/*}}}*/
1769 
handle_focus(AstroidMessages::Focus & msg)1770 void AstroidExtension::handle_focus (AstroidMessages::Focus &msg) {
1771   apply_focus (msg.mid (), msg.element ());
1772   ack (true);
1773 }
1774 
apply_focus(ustring mid,int element)1775 void AstroidExtension::apply_focus (ustring mid, int element) {
1776   LOG (debug) << "focusing: " << mid << ": " << element;
1777   focused_message = mid;
1778   focused_element = element;
1779 
1780   if (focused_message.empty() || focused_element == -1) return;
1781 
1782   WebKitDOMDocument *d = webkit_web_page_get_dom_document (page);
1783 
1784   for (auto &m : state.messages()) {
1785 
1786     ustring _mid = "message_" + m.mid ();
1787 
1788     WebKitDOMElement * me = webkit_dom_document_get_element_by_id (d, _mid.c_str ());
1789     WebKitDOMDOMTokenList * class_list = webkit_dom_element_get_class_list (me);
1790 
1791     /* set class  */
1792     DomUtils::switch_class (class_list, "focused", m.mid () == mid);
1793 
1794     g_object_unref (class_list);
1795 
1796     int ei = 0;
1797     for (auto &e : m.elements ()) {
1798       if (
1799           // all states contain an empty element at first
1800           e.type() != AstroidMessages::State_MessageState_Element_Type_Empty
1801 
1802           // skip elements that cannot be focused
1803           && e.focusable() == true
1804          ) {
1805 
1806         WebKitDOMElement * ee = webkit_dom_document_get_element_by_id (d, e.sid().c_str());
1807         WebKitDOMDOMTokenList * e_class_list =
1808           webkit_dom_element_get_class_list (ee);
1809 
1810         DomUtils::switch_class (e_class_list, "focused", (m.mid () == mid && ei == element));
1811 
1812         g_object_unref (e_class_list);
1813         g_object_unref (ee);
1814       }
1815 
1816       ei++;
1817     }
1818 
1819     g_object_unref (me);
1820   }
1821 
1822   g_object_unref (d);
1823 
1824   LOG (debug) << "focus done.";
1825 }
1826 
update_focus_to_view()1827 void AstroidExtension::update_focus_to_view () {
1828   /* check if currently focused message has gone out of focus
1829    * and update focus */
1830 
1831   /* loop through elements from the top and test whether the top
1832    * of it is within the view
1833    */
1834 
1835   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1836   WebKitDOMDOMWindow * w = webkit_dom_document_get_default_view (d);
1837   WebKitDOMElement * body = WEBKIT_DOM_ELEMENT(webkit_dom_document_get_body (d));
1838 
1839   double scrolled = webkit_dom_dom_window_get_scroll_y (w);
1840   double height   = webkit_dom_element_get_client_height (body);
1841 
1842   //LOG (debug) << "scrolled = " << scrolled << ", height = " << height;
1843 
1844   /* check currently focused message */
1845   bool take_next = false;
1846 
1847   /* need to apply_focus afterwards */
1848   bool redo_focus = false;
1849 
1850   /* take first if none focused */
1851   if (focused_message.empty ()) {
1852     focused_message = state.messages(0).mid() ;
1853     redo_focus = true;
1854   }
1855 
1856   /* check if focused message is still visible */
1857   ustring mid = "message_" + focused_message;
1858 
1859   WebKitDOMElement * e = webkit_dom_document_get_element_by_id (d, mid.c_str());
1860 
1861   double clientY = webkit_dom_element_get_offset_top (e);
1862   double clientH = webkit_dom_element_get_client_height (e);
1863 
1864   g_object_unref (e);
1865 
1866   //LOG (debug) << "y = " << clientY;
1867   //LOG (debug) << "h = " << clientH;
1868 
1869   // height = 0 if there is no paging: all messages are in view.
1870   if ((height == 0) || ( (clientY <= (scrolled + height)) && ((clientY + clientH) >= scrolled) )) {
1871     //LOG (debug) << "message: " << focused_message->date() << " still in view.";
1872   } else {
1873     //LOG (debug) << "message: " << focused_message->date() << " out of view.";
1874     take_next = true;
1875   }
1876 
1877   /* find first message that is in view and update
1878    * focused status */
1879   if (take_next) {
1880     int focused_position = std::find_if (
1881         state.messages().begin (),
1882         state.messages().end (),
1883         [&] (auto &m) { return m.mid () == focused_message; }) - state.messages().begin ();
1884     int cur_position = 0;
1885 
1886     bool found = false;
1887 
1888     for (auto &m : state.messages()) {
1889       ustring mid = "message_" + m.mid();
1890 
1891       WebKitDOMElement * e = webkit_dom_document_get_element_by_id (d, mid.c_str());
1892 
1893       double clientY = webkit_dom_element_get_offset_top (e);
1894       double clientH = webkit_dom_element_get_client_height (e);
1895 
1896       // LOG (debug) << "y = " << clientY;
1897       // LOG (debug) << "h = " << clientH;
1898 
1899       /* search for the last message that is currently in view
1900        * if the focused message is now below / beyond the view.
1901        * otherwise, take first that is in view now. */
1902 
1903       if ((!found || cur_position < focused_position) &&
1904           ( (height == 0) || ((clientY <= (scrolled + height)) && ((clientY + clientH) >= scrolled)) ))
1905       {
1906         // LOG (debug) << "message: " << m->date() << " now in view.";
1907 
1908         if (found) redo_focus = true;
1909         found = true;
1910         focused_message = m.mid();
1911         focused_element = 0;
1912 
1913       } else {
1914         /* reset class */
1915         redo_focus = true;
1916       }
1917 
1918       g_object_unref (e);
1919 
1920       cur_position++;
1921     }
1922 
1923     g_object_unref (body);
1924     g_object_unref (w);
1925     g_object_unref (d);
1926 
1927     if (redo_focus) apply_focus (focused_message, focused_element);
1928   }
1929 
1930 }
1931 
focus_next_element(bool force_change)1932 void AstroidExtension::focus_next_element (bool force_change) {
1933   ustring eid;
1934   LOG (debug) << "fne: " << focused_message << ", " << focused_element;
1935 
1936   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
1937   WebKitDOMDOMWindow * w = webkit_dom_document_get_default_view (d);
1938   WebKitDOMElement * body = WEBKIT_DOM_ELEMENT(webkit_dom_document_get_body (d));
1939 
1940   /* force change if we are at maximum scroll */
1941   long scroll_height = webkit_dom_element_get_scroll_height (body);
1942   long scroll_top    = webkit_dom_element_get_scroll_top (body);
1943   long body_height   = webkit_dom_element_get_client_height (body);
1944 
1945   force_change = force_change ||
1946     (scroll_height - scroll_top == body_height);
1947 
1948 
1949   if (!is_hidden (focused_message)) {
1950     /* if the message is expanded, check if we should move focus
1951      * to the next element */
1952 
1953     auto s = *std::find_if (
1954         state.messages().begin (),
1955         state.messages().end (),
1956         [&] (auto &m) { return m.mid () == focused_message; });
1957 
1958     /* are there any more focusable elements */
1959     auto next_e = std::find_if (
1960         s.elements().begin () + (focused_element +1),
1961         s.elements().end (),
1962         [&] (auto &e) { return e.focusable (); });
1963 
1964     if (next_e != s.elements().end ()) {
1965       /* check if the next element is in full view */
1966       eid = next_e->sid ();
1967 
1968       LOG (debug) << "next_e: " << eid;
1969 
1970       if (force_change || DomUtils::in_view (page, eid)) {
1971         /* move focus to next element and scroll if necessary */
1972         focused_element = std::distance (s.elements ().begin (), next_e);
1973         apply_focus (focused_message, focused_element);
1974 
1975         scroll_to_element (eid);
1976         g_object_unref (body);
1977         g_object_unref (w);
1978         g_object_unref (d);
1979         return;
1980       }
1981 
1982       /* fall through to scroll */
1983     } else {
1984       /* move focus to next message if in view or force_change */
1985       auto s = std::find_if (
1986           state.messages().begin (),
1987           state.messages().end (),
1988           [&] (auto &m) { return m.mid () == focused_message; });
1989 
1990       s++;
1991 
1992       if (s < state.messages().end () &&
1993           (force_change || DomUtils::in_view (page, "message_" + s->mid ()))) {
1994 
1995         focus_next_message ();
1996 
1997         g_object_unref (body);
1998         g_object_unref (w);
1999         g_object_unref (d);
2000         return;
2001       }
2002       /* fall through to scroll */
2003     }
2004   } else {
2005     /* move focus to next message if in view or force_change */
2006 
2007     auto s = std::find_if (
2008         state.messages().begin (),
2009         state.messages().end (),
2010         [&] (auto &m) { return m.mid () == focused_message; });
2011 
2012     s++;
2013 
2014     if (s < state.messages().end () &&
2015         (force_change || DomUtils::in_view (page, "message_" + s->mid ()))) {
2016 
2017       focus_next_message ();
2018 
2019       g_object_unref (body);
2020       g_object_unref (w);
2021       g_object_unref (d);
2022       return;
2023     }
2024     /* fall through to scroll */
2025   }
2026 
2027   /* no focus change, standard behaviour */
2028 
2029   webkit_dom_dom_window_scroll_by (w, 0, STEP);
2030 
2031   if (!force_change) {
2032     update_focus_to_view ();
2033   }
2034 
2035   g_object_unref (body);
2036   g_object_unref (w);
2037   g_object_unref (d);
2038 }
2039 
focus_previous_element(bool force_change)2040 void AstroidExtension::focus_previous_element (bool force_change) {
2041   ustring eid;
2042 
2043   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
2044   WebKitDOMDOMWindow * w = webkit_dom_document_get_default_view (d);
2045   WebKitDOMElement * body = WEBKIT_DOM_ELEMENT(webkit_dom_document_get_body (d));
2046 
2047   /* force change if scrolled to top */
2048   long scroll_top = webkit_dom_element_get_scroll_top (body);
2049   force_change    = force_change || (scroll_top == 0);
2050 
2051 
2052 
2053   if (!is_hidden (focused_message)) {
2054     /* if the message is expanded, check if we should move focus
2055      * to the previous element */
2056 
2057     LOG (debug) << "fpe: prev, not hidden";
2058 
2059     auto s = *std::find_if (
2060         state.messages().begin (),
2061         state.messages().end (),
2062         [&] (auto &m) { return m.mid () == focused_message; });
2063 
2064     /* focus previous focusable element */
2065     auto next_e = std::find_if (
2066         s.elements().rbegin() + (s.elements().size() - focused_element),
2067         s.elements().rend (),
2068         [&] (auto &e) { return e.focusable (); });
2069 
2070 
2071     if (next_e != s.elements().rend()) {
2072       /* check if the prev element is in full view */
2073       eid = next_e->sid ();
2074       LOG (debug) << "fpe: more prev items: " << eid;
2075 
2076       if (force_change || DomUtils::in_view (page, eid)) {
2077 
2078         focused_element = std::distance (s.elements ().begin (), next_e.base() -1);
2079 
2080         apply_focus (focused_message, focused_element);
2081 
2082         if (next_e->id() != -1) {
2083           /* if part element (always id == -1), don't scroll to empty part */
2084           scroll_to_element (eid);
2085         }
2086 
2087         g_object_unref (body);
2088         g_object_unref (w);
2089         g_object_unref (d);
2090         return;
2091       }
2092 
2093       /* fall through to scroll */
2094 
2095     }  else {
2096       /* focus previous message if in view */
2097       auto s = std::find_if (
2098           state.messages().begin (),
2099           state.messages().end (),
2100           [&] (auto &m) { return m.mid () == focused_message; });
2101 
2102       s--;
2103 
2104       if (s >= state.messages().begin () && (
2105             force_change || DomUtils::in_view (page, "message_" + s->mid ()) )) {
2106 
2107         focus_previous_message (false);
2108 
2109         g_object_unref (body);
2110         g_object_unref (w);
2111         g_object_unref (d);
2112         return;
2113       }
2114 
2115       /* fall through to scroll */
2116     }
2117   } else {
2118     /* move focus to previous message if in view or force_change */
2119     auto s = std::find_if (
2120         state.messages().begin (),
2121         state.messages().end (),
2122         [&] (auto &m) { return m.mid () == focused_message; });
2123 
2124     s--;
2125 
2126     if (s >= state.messages().begin () && (
2127           force_change || DomUtils::in_view (page, "message_" + s->mid ()) )) {
2128 
2129       focus_previous_message (false);
2130 
2131       g_object_unref (body);
2132       g_object_unref (w);
2133       g_object_unref (d);
2134       return;
2135     }
2136     /* fall through to scroll */
2137   }
2138 
2139   /* standard behaviour */
2140   webkit_dom_dom_window_scroll_by (w, 0, -STEP);
2141 
2142   if (!force_change) {
2143     /* we have scrolled */
2144     update_focus_to_view ();
2145   }
2146 
2147   g_object_unref (body);
2148   g_object_unref (w);
2149   g_object_unref (d);
2150 }
2151 
focus_next_message()2152 void AstroidExtension::focus_next_message () {
2153   if (edit_mode) return;
2154 
2155   auto s = std::find_if (
2156         state.messages().begin (),
2157         state.messages().end (),
2158         [&] (auto &m) { return m.mid () == focused_message; });
2159 
2160   s++;
2161 
2162   if (s < state.messages().end ()) {
2163     focused_message = s->mid ();
2164     focused_element = 0; // start at top
2165     apply_focus (focused_message, focused_element);
2166     scroll_to_element ("message_" + focused_message);
2167   }
2168 }
2169 
focus_previous_message(bool focus_top)2170 void AstroidExtension::focus_previous_message (bool focus_top) {
2171   if (edit_mode) return;
2172   LOG (debug) << "prev message";
2173 
2174   auto s = std::find_if (
2175         state.messages().begin (),
2176         state.messages().end (),
2177         [&] (auto &m) { return m.mid () == focused_message; });
2178 
2179   s--;
2180 
2181   if (s >= state.messages().begin ()) {
2182     focused_message = s->mid();
2183     if (!focus_top && !is_hidden (focused_message)) {
2184       /* focus last focusable element */
2185       auto next_e = std::find_if (
2186           s->elements().rbegin(),
2187           s->elements().rend (),
2188           [&] (auto &e) { return e.focusable (); });
2189 
2190       if (next_e != s->elements ().rend ()) {
2191 
2192         focused_element = std::distance (s->elements ().begin (), next_e.base() -1);
2193 
2194       } else {
2195         focused_element = 0;
2196       }
2197     } else {
2198       focused_element = 0;
2199     }
2200 
2201     apply_focus (focused_message, focused_element);
2202     scroll_to_element ("message_" + focused_message);
2203   }
2204 }
2205 
scroll_to_element(ustring eid)2206 void AstroidExtension::scroll_to_element (ustring eid) {
2207   LOG (debug) << "scrolling to: " << eid;
2208   if (eid.empty()) {
2209     LOG (debug) << "attempted to scroll to unspecified id.";
2210     return;
2211   }
2212 
2213   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
2214   WebKitDOMElement * e = webkit_dom_document_get_element_by_id (d, eid.c_str());
2215 
2216   webkit_dom_element_scroll_into_view_if_needed (e, false);
2217 
2218   g_object_unref (e);
2219   g_object_unref (d);
2220   return;
2221 }
2222 
handle_navigate(AstroidMessages::Navigate & n)2223 void AstroidExtension::handle_navigate (AstroidMessages::Navigate &n) {
2224   std::string _t = AstroidMessages::Navigate_Type_descriptor ()->FindValueByNumber (n.type ())->name ();
2225   LOG (debug) << "navigating, type: " << _t;
2226 
2227   WebKitDOMDocument * d = webkit_web_page_get_dom_document (page);
2228   WebKitDOMDOMWindow * w = webkit_dom_document_get_default_view (d);
2229 
2230   if (n.type () == AstroidMessages::Navigate_Type_VisualBig) {
2231 
2232     if (n.direction () == AstroidMessages::Navigate_Direction_Down) {
2233       webkit_dom_dom_window_scroll_by (w, 0, BIG_JUMP);
2234     } else {
2235       webkit_dom_dom_window_scroll_by (w, 0, -BIG_JUMP);
2236     }
2237     update_focus_to_view ();
2238 
2239   } else if (n.type () == AstroidMessages::Navigate_Type_VisualPage) {
2240 
2241     if (n.direction () == AstroidMessages::Navigate_Direction_Down) {
2242       webkit_dom_dom_window_scroll_by (w, 0, PAGE_JUMP);
2243     } else {
2244       webkit_dom_dom_window_scroll_by (w, 0, -PAGE_JUMP);
2245     }
2246     update_focus_to_view ();
2247 
2248   } else if (n.type () == AstroidMessages::Navigate_Type_VisualElement) {
2249 
2250     if (n.direction () == AstroidMessages::Navigate_Direction_Down) {
2251       focus_next_element (false);
2252     } else {
2253       focus_previous_element (false);
2254     }
2255 
2256   } else if (n.type () == AstroidMessages::Navigate_Type_Element) {
2257 
2258     if (n.direction () == AstroidMessages::Navigate_Direction_Specific) {
2259       apply_focus (n.mid(), n.element ());
2260       if (n.element () == 0) {
2261         scroll_to_element ("message_" + n.mid());
2262       } else {
2263         scroll_to_element (ustring::compose ("%1", n.element ()));
2264       }
2265 
2266     } else if (n.direction () == AstroidMessages::Navigate_Direction_Down) {
2267       focus_next_element (true);
2268     } else {
2269       focus_previous_element (true);
2270     }
2271 
2272   } else if (n.type () == AstroidMessages::Navigate_Type_Message) {
2273     if (n.direction () == AstroidMessages::Navigate_Direction_Down) {
2274       focus_next_message ();
2275     } else {
2276       focus_previous_message (n.focus_top());
2277     }
2278 
2279   } else if (n.type () == AstroidMessages::Navigate_Type_FocusView) {
2280     update_focus_to_view ();
2281 
2282   } else if (n.type () == AstroidMessages::Navigate_Type_Extreme) {
2283     if (n.direction () == AstroidMessages::Navigate_Direction_Down) {
2284       WebKitDOMElement * body = WEBKIT_DOM_ELEMENT(webkit_dom_document_get_body (d));
2285       double scroll_height = webkit_dom_element_get_scroll_height (body);
2286 
2287       webkit_dom_dom_window_scroll_to (w, 0, scroll_height);
2288 
2289       /* focus last element of last message */
2290       auto s = --state.messages ().end ();
2291       focused_message = s->mid ();
2292 
2293       if (is_hidden (focused_message)) {
2294         focused_element = 0;
2295       } else {
2296         auto next_e = std::find_if (
2297             s->elements().rbegin(),
2298             s->elements().rend (),
2299             [&] (auto &e) { return e.focusable (); });
2300 
2301         if (next_e != s->elements ().rend ()) {
2302 
2303           focused_element = std::distance (s->elements ().begin (), next_e.base() -1);
2304 
2305         } else {
2306           focused_element = 0;
2307         }
2308       }
2309 
2310       apply_focus (focused_message, focused_element);
2311 
2312       g_object_unref (body);
2313     } else {
2314       webkit_dom_dom_window_scroll_to (w, 0, 0);
2315       apply_focus (state.messages().begin()->mid (), 0);
2316     }
2317   }
2318 
2319   LOG (debug) << "navigation done.";
2320 
2321   g_object_unref (w);
2322   g_object_unref (d);
2323 
2324   ack (true);
2325 }
2326 
2327