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 <%2></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