Revert "Revert "Merge branch 'mjog/558-webkit-shared-process' into 'mainline'""

This reverts commit cbe6e0ba9b, which reinstates
commit e4a5b85698.

See !411 and !374
This commit is contained in:
Michael James Gratton 2020-08-25 22:10:28 +10:00
parent d0fff267f8
commit d7af23201c
28 changed files with 418 additions and 245 deletions

View file

@ -88,6 +88,14 @@
<translation type="gettext">geary</translation> <translation type="gettext">geary</translation>
<releases> <releases>
<release version="3.40" date="">
<description>
<p>Enhancements included in this release:</p>
<ul>
<li>Conversation loading performance improvements</li>
</ul>
</description>
</release>
<release version="3.38" date="2020-09-13"> <release version="3.38" date="2020-09-13">
<description> <description>
<p>Enhancements included in this release:</p> <p>Enhancements included in this release:</p>

View file

@ -37,7 +37,6 @@ src/client/application/goa-mediator.vala
src/client/application/main.vala src/client/application/main.vala
src/client/application/secret-mediator.vala src/client/application/secret-mediator.vala
src/client/client-action.vala src/client/client-action.vala
src/client/components/client-web-view.vala
src/client/components/components-attachment-pane.vala src/client/components/components-attachment-pane.vala
src/client/components/components-conversation-actions.vala src/client/components/components-conversation-actions.vala
src/client/components/components-conversation-action-bar.vala src/client/components/components-conversation-action-bar.vala
@ -52,6 +51,7 @@ src/client/components/components-problem-report-info-bar.vala
src/client/components/components-reflow-box.c src/client/components/components-reflow-box.c
src/client/components/components-search-bar.vala src/client/components/components-search-bar.vala
src/client/components/components-validator.vala src/client/components/components-validator.vala
src/client/components/components-web-view.vala
src/client/components/count-badge.vala src/client/components/count-badge.vala
src/client/components/folder-popover.vala src/client/components/folder-popover.vala
src/client/components/icon-factory.vala src/client/components/icon-factory.vala

View file

@ -718,7 +718,7 @@ internal class Accounts.RemoveMailboxCommand : Application.Command {
internal class Accounts.SignatureChangedCommand : Application.Command { internal class Accounts.SignatureChangedCommand : Application.Command {
private ClientWebView signature_view; private Components.WebView signature_view;
private Geary.AccountInformation account; private Geary.AccountInformation account;
private string old_value; private string old_value;
@ -728,7 +728,7 @@ internal class Accounts.SignatureChangedCommand : Application.Command {
private bool new_enabled = false; private bool new_enabled = false;
public SignatureChangedCommand(ClientWebView signature_view, public SignatureChangedCommand(Components.WebView signature_view,
Geary.AccountInformation account) { Geary.AccountInformation account) {
this.signature_view = signature_view; this.signature_view = signature_view;
this.account = account; this.account = account;

View file

@ -8,14 +8,14 @@
/** /**
* A class for editing signatures in the accounts editor. * A class for editing signatures in the accounts editor.
*/ */
public class Accounts.SignatureWebView : ClientWebView { public class Accounts.SignatureWebView : Components.WebView {
private static WebKit.UserScript? app_script = null; private static WebKit.UserScript? app_script = null;
public static new void load_resources() public static new void load_resources()
throws GLib.Error { throws GLib.Error {
SignatureWebView.app_script = ClientWebView.load_app_script( SignatureWebView.app_script = Components.WebView.load_app_script(
"signature-web-view.js" "signature-web-view.js"
); );
} }

View file

@ -134,13 +134,12 @@ internal class Application.Controller :
this.upgrade_dialog = new UpgradeDialog(application); this.upgrade_dialog = new UpgradeDialog(application);
// Initialise WebKit and WebViews // Initialise WebKit and WebViews
ClientWebView.init_web_context( Components.WebView.init_web_context(
this.application.config, this.application.config,
this.application.get_web_extensions_dir(), this.application.get_web_extensions_dir(),
this.application.get_user_cache_directory().get_child("web-resources") this.application.get_user_cache_directory().get_child("web-resources")
); );
Components.WebView.load_resources(
ClientWebView.load_resources(
this.application.get_user_config_directory() this.application.get_user_config_directory()
); );
Composer.WebView.load_resources(); Composer.WebView.load_resources();

View file

@ -5,12 +5,6 @@
*/ */
int main(string[] args) { int main(string[] args) {
// Temporary workaround for WebKitGTK deprecation of the
// shared-secondary process model. Pull this out in 3.36 when the
// proper fix lands. See GNOME/geary#558.
Environment.set_variable("WEBKIT_USE_SINGLE_WEB_PROCESS", "1", true);
// Init logging right up front so as to capture as many log // Init logging right up front so as to capture as many log
// messages as possible // messages as possible
Geary.Logging.init(); Geary.Logging.init();

View file

@ -1,6 +1,6 @@
/* /*
* Copyright 2016 Software Freedom Conservancy Inc. * Copyright 2016 Software Freedom Conservancy Inc.
* Copyright 2016 Michael Gratton <mike@vee.net> * Copyright 2016-2019 Michael Gratton <mike@vee.net>
* *
* This software is licensed under the GNU Lesser General Public License * This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution. * (version 2.1 or later). See the COPYING file in this distribution.
@ -14,7 +14,7 @@
* integration, Inspector support, and remote and inline image * integration, Inspector support, and remote and inline image
* handling. * handling.
*/ */
public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface { public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
/** URI Scheme and delimiter for internal resource loads. */ /** URI Scheme and delimiter for internal resource loads. */
@ -65,7 +65,6 @@ public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface {
private static WebKit.UserStyleSheet? user_stylesheet = null; private static WebKit.UserStyleSheet? user_stylesheet = null;
private static WebKit.UserScript? script = null; private static WebKit.UserScript? script = null;
private static WebKit.UserScript? allow_remote_images = null;
/** /**
@ -76,23 +75,18 @@ public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface {
File cache_dir) { File cache_dir) {
WebsiteDataManager data_manager = new WebsiteDataManager(cache_dir.get_path()); WebsiteDataManager data_manager = new WebsiteDataManager(cache_dir.get_path());
WebKit.WebContext context = new WebKit.WebContext.with_website_data_manager(data_manager); WebKit.WebContext context = new WebKit.WebContext.with_website_data_manager(data_manager);
#if HAS_WEBKIT_SHARED_PROC
// Use a shared process so we don't spawn N WebProcess instances
// when showing N messages in a conversation.
context.set_process_model(WebKit.ProcessModel.SHARED_SECONDARY_PROCESS);
#endif
// Use the doc viewer model since each web view instance only // Use the doc viewer model since each web view instance only
// ever shows a single HTML document. // ever shows a single HTML document.
context.set_cache_model(WebKit.CacheModel.DOCUMENT_VIEWER); context.set_cache_model(WebKit.CacheModel.DOCUMENT_VIEWER);
context.register_uri_scheme("cid", (req) => { context.register_uri_scheme("cid", (req) => {
ClientWebView? view = req.get_web_view() as ClientWebView; WebView? view = req.get_web_view() as WebView;
if (view != null) { if (view != null) {
view.handle_cid_request(req); view.handle_cid_request(req);
} }
}); });
context.register_uri_scheme("geary", (req) => { context.register_uri_scheme("geary", (req) => {
ClientWebView? view = req.get_web_view() as ClientWebView; WebView? view = req.get_web_view() as WebView;
if (view != null) { if (view != null) {
view.handle_internal_request(req); view.handle_internal_request(req);
} }
@ -113,25 +107,22 @@ public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface {
update_spellcheck(context, config); update_spellcheck(context, config);
}); });
ClientWebView.default_context = context; WebView.default_context = context;
} }
/** /**
* Loads static resources used by ClientWebView. * Loads static resources used by WebView.
*/ */
public static void load_resources(GLib.File user_dir) public static void load_resources(GLib.File user_dir)
throws GLib.Error { throws GLib.Error {
ClientWebView.script = load_app_script( WebView.script = load_app_script(
"client-web-view.js" "components-web-view.js"
);
ClientWebView.allow_remote_images = load_app_script(
"client-web-view-allow-remote-images.js"
); );
foreach (string name in new string[] { USER_CSS, USER_CSS_LEGACY }) { foreach (string name in new string[] { USER_CSS, USER_CSS_LEGACY }) {
GLib.File stylesheet = user_dir.get_child(name); GLib.File stylesheet = user_dir.get_child(name);
try { try {
ClientWebView.user_stylesheet = load_user_stylesheet(stylesheet); WebView.user_stylesheet = load_user_stylesheet(stylesheet);
break; break;
} catch (GLib.IOError.NOT_FOUND err) { } catch (GLib.IOError.NOT_FOUND err) {
// All good, try the next one or just exit // All good, try the next one or just exit
@ -299,8 +290,9 @@ public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface {
public signal void remote_image_load_blocked(); public signal void remote_image_load_blocked();
protected ClientWebView(Application.Configuration config, protected WebView(Application.Configuration config,
WebKit.UserContentManager? custom_manager = null) { WebKit.UserContentManager? custom_manager = null,
WebView? related = null) {
WebKit.Settings setts = new WebKit.Settings(); WebKit.Settings setts = new WebKit.Settings();
setts.allow_modal_dialogs = false; setts.allow_modal_dialogs = false;
setts.default_charset = "UTF-8"; setts.default_charset = "UTF-8";
@ -321,62 +313,40 @@ public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface {
WebKit.UserContentManager content_manager = WebKit.UserContentManager content_manager =
custom_manager ?? new WebKit.UserContentManager(); custom_manager ?? new WebKit.UserContentManager();
content_manager.add_script(ClientWebView.script); content_manager.add_script(WebView.script);
if (ClientWebView.user_stylesheet != null) { if (WebView.user_stylesheet != null) {
content_manager.add_style_sheet(ClientWebView.user_stylesheet); content_manager.add_style_sheet(WebView.user_stylesheet);
} }
Object( Object(
web_context: ClientWebView.default_context, settings: setts,
user_content_manager: content_manager, user_content_manager: content_manager,
settings: setts web_context: WebView.default_context
); );
base_ref(); base_ref();
init(config);
// XXX get the allow prefix from the extension somehow
this.decide_policy.connect(on_decide_policy);
this.web_process_terminated.connect((reason) => {
warning("Web process crashed: %s", reason.to_string());
});
register_message_handler(
COMMAND_STACK_CHANGED, on_command_stack_changed
);
register_message_handler(
CONTENT_LOADED, on_content_loaded
);
register_message_handler(
DOCUMENT_MODIFIED, on_document_modified
);
register_message_handler(
PREFERRED_HEIGHT_CHANGED, on_preferred_height_changed
);
register_message_handler(
REMOTE_IMAGE_LOAD_BLOCKED, on_remote_image_load_blocked
);
register_message_handler(
SELECTION_CHANGED, on_selection_changed
);
// Manage zoom level, ensure it's sane
config.bind(Application.Configuration.CONVERSATION_VIEWER_ZOOM_KEY, this, "zoom_level");
if (this.zoom_level < ZOOM_MIN) {
this.zoom_level = ZOOM_MIN;
} else if (this.zoom_level > ZOOM_MAX) {
this.zoom_level = ZOOM_MAX;
}
this.scroll_event.connect(on_scroll_event);
// Watch desktop font settings
Settings system_settings = config.gnome_interface;
system_settings.bind("document-font-name", this,
"document-font", SettingsBindFlags.DEFAULT);
system_settings.bind("monospace-font-name", this,
"monospace-font", SettingsBindFlags.DEFAULT);
} }
~ClientWebView() { /**
* Constructs a new web view with a new shared WebProcess.
*
* The new view will use the same WebProcess, settings and content
* manager as the given related view's.
*
* @see WebKit.WebView.with_related_view
*/
protected WebView.with_related_view(Application.Configuration config,
WebView related) {
Object(
related_view: related,
settings: related.get_settings(),
user_content_manager: related.user_content_manager
);
base_ref();
init(config);
}
~WebView() {
base_unref(); base_unref();
} }
@ -433,13 +403,7 @@ public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface {
* effect. * effect.
*/ */
public void allow_remote_image_loading() { public void allow_remote_image_loading() {
// Use a separate script here since we need to update the this.run_javascript.begin("_gearyAllowRemoteResourceLoads = true", null);
// value of window.geary.allow_remote_image_loading after it
// was first created by client-web-view.js (which is loaded at
// the start of page load), but before the page load is
// started (so that any remote images present are actually
// loaded).
this.user_content_manager.add_script(ClientWebView.allow_remote_images);
} }
/** /**
@ -515,7 +479,7 @@ public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface {
JavaScriptMessageHandler handler) { JavaScriptMessageHandler handler) {
// XXX can't use the delegate directly, see b.g.o Bug // XXX can't use the delegate directly, see b.g.o Bug
// 604781. However the workaround below creates a circular // 604781. However the workaround below creates a circular
// reference, causing ClientWebView instances to leak. So to // reference, causing WebView instances to leak. So to
// work around that we need to record handler ids and // work around that we need to record handler ids and
// disconnect them when being destroyed. // disconnect them when being destroyed.
ulong id = this.user_content_manager.script_message_received[name].connect( ulong id = this.user_content_manager.script_message_received[name].connect(
@ -527,6 +491,50 @@ public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface {
} }
} }
private void init(Application.Configuration config) {
// XXX get the allow prefix from the extension somehow
this.decide_policy.connect(on_decide_policy);
this.web_process_terminated.connect((reason) => {
warning("Web process crashed: %s", reason.to_string());
});
register_message_handler(
COMMAND_STACK_CHANGED, on_command_stack_changed
);
register_message_handler(
CONTENT_LOADED, on_content_loaded
);
register_message_handler(
DOCUMENT_MODIFIED, on_document_modified
);
register_message_handler(
PREFERRED_HEIGHT_CHANGED, on_preferred_height_changed
);
register_message_handler(
REMOTE_IMAGE_LOAD_BLOCKED, on_remote_image_load_blocked
);
register_message_handler(
SELECTION_CHANGED, on_selection_changed
);
// Manage zoom level, ensure it's sane
config.bind(Application.Configuration.CONVERSATION_VIEWER_ZOOM_KEY, this, "zoom_level");
if (this.zoom_level < ZOOM_MIN) {
this.zoom_level = ZOOM_MIN;
} else if (this.zoom_level > ZOOM_MAX) {
this.zoom_level = ZOOM_MAX;
}
this.scroll_event.connect(on_scroll_event);
// Watch desktop font settings
Settings system_settings = config.gnome_interface;
system_settings.bind("document-font-name", this,
"document-font", SettingsBindFlags.DEFAULT);
system_settings.bind("monospace-font-name", this,
"monospace-font", SettingsBindFlags.DEFAULT);
}
private void handle_cid_request(WebKit.URISchemeRequest request) { private void handle_cid_request(WebKit.URISchemeRequest request) {
if (!handle_internal_response(request)) { if (!handle_internal_response(request)) {
request.finish_error(new FileError.NOENT("Unknown CID")); request.finish_error(new FileError.NOENT("Unknown CID"));

View file

@ -9,7 +9,7 @@
/** /**
* A WebView for editing messages in the composer. * A WebView for editing messages in the composer.
*/ */
public class Composer.WebView : ClientWebView { public class Composer.WebView : Components.WebView {
/** HTML id used for the main text section of the message body. */ /** HTML id used for the main text section of the message body. */
public const string BODY_HTML_ID = "geary-body"; public const string BODY_HTML_ID = "geary-body";
@ -112,10 +112,10 @@ public class Composer.WebView : ClientWebView {
public static new void load_resources() public static new void load_resources()
throws Error { throws Error {
WebView.app_style = ClientWebView.load_app_stylesheet( WebView.app_style = Components.WebView.load_app_stylesheet(
"composer-web-view.css" "composer-web-view.css"
); );
WebView.app_script = ClientWebView.load_app_script( WebView.app_script = Components.WebView.load_app_script(
"composer-web-view.js" "composer-web-view.js"
); );
} }

View file

@ -1252,7 +1252,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
email.inline_files.set_all(this.inline_files); email.inline_files.set_all(this.inline_files);
email.cid_files.set_all(this.cid_files); email.cid_files.set_all(this.cid_files);
email.img_src_prefix = ClientWebView.INTERNAL_URL_PREFIX; email.img_src_prefix = Components.WebView.INTERNAL_URL_PREFIX;
try { try {
email.body_text = yield this.editor.body.get_text(); email.body_text = yield this.editor.body.get_text();
@ -1924,7 +1924,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
string unique_filename; string unique_filename;
add_inline_part(byte_buffer, filename, out unique_filename); add_inline_part(byte_buffer, filename, out unique_filename);
this.editor.body.insert_image( this.editor.body.insert_image(
ClientWebView.INTERNAL_URL_PREFIX + unique_filename Components.WebView.INTERNAL_URL_PREFIX + unique_filename
); );
} catch (Error error) { } catch (Error error) {
this.application.report_problem( this.application.report_problem(
@ -1964,7 +1964,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
string unique_filename; string unique_filename;
add_inline_part(file_buffer, path, out unique_filename); add_inline_part(file_buffer, path, out unique_filename);
this.editor.body.insert_image( this.editor.body.insert_image(
ClientWebView.INTERNAL_URL_PREFIX + unique_filename Components.WebView.INTERNAL_URL_PREFIX + unique_filename
); );
} catch (Error err) { } catch (Error err) {
attachment_failed(err.message); attachment_failed(err.message);
@ -2459,7 +2459,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
} }
this.editor.body.insert_image( this.editor.body.insert_image(
ClientWebView.INTERNAL_URL_PREFIX + unique_filename Components.WebView.INTERNAL_URL_PREFIX + unique_filename
); );
} }

View file

@ -462,7 +462,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
if (this.body_selection_message != null) { if (this.body_selection_message != null) {
try { try {
selection = selection =
yield this.body_selection_message.web_view.get_selection_for_quoting(); yield this.body_selection_message.get_selection_for_quoting();
} catch (Error err) { } catch (Error err) {
debug("Failed to get selection for quoting: %s", err.message); debug("Failed to get selection for quoting: %s", err.message);
} }
@ -478,7 +478,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
if (this.body_selection_message != null) { if (this.body_selection_message != null) {
try { try {
selection = selection =
yield this.body_selection_message.web_view.get_selection_for_find(); yield this.body_selection_message.get_selection_for_find();
} catch (Error err) { } catch (Error err) {
debug("Failed to get selection for find: %s", err.message); debug("Failed to get selection for find: %s", err.message);
} }
@ -571,12 +571,10 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
Json.Generator generator = new Json.Generator(); Json.Generator generator = new Json.Generator();
generator.set_root(builder.get_root()); generator.set_root(builder.get_root());
string js = "geary.addPrintHeaders(" + generator.to_data(null) + ");"; string js = "geary.addPrintHeaders(" + generator.to_data(null) + ");";
yield this.primary_message.web_view.run_javascript(js, null); yield this.primary_message.run_javascript(js, null);
Gtk.Window? window = get_toplevel() as Gtk.Window; Gtk.Window? window = get_toplevel() as Gtk.Window;
WebKit.PrintOperation op = new WebKit.PrintOperation( WebKit.PrintOperation op = this.primary_message.new_print_operation();
this.primary_message.web_view
);
Gtk.PrintSettings settings = new Gtk.PrintSettings(); Gtk.PrintSettings settings = new Gtk.PrintSettings();
if (this.email.subject != null) { if (this.email.subject != null) {
@ -603,14 +601,14 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
} }
private void connect_message_view_signals(ConversationMessage view) { private void connect_message_view_signals(ConversationMessage view) {
view.content_loaded.connect(on_content_loaded);
view.flag_remote_images.connect(on_flag_remote_images); view.flag_remote_images.connect(on_flag_remote_images);
view.internal_link_activated.connect((y) => { view.internal_link_activated.connect((y) => {
internal_link_activated(y); internal_link_activated(y);
}); });
view.internal_resource_loaded.connect(on_resource_loaded);
view.save_image.connect(on_save_image); view.save_image.connect(on_save_image);
view.web_view.internal_resource_loaded.connect(on_resource_loaded); view.selection_changed.connect((has_selection) => {
view.web_view.content_loaded.connect(on_content_loaded);
view.web_view.selection_changed.connect((has_selection) => {
this.body_selection_message = has_selection ? view : null; this.body_selection_message = has_selection ? view : null;
body_selection_changed(has_selection); body_selection_changed(has_selection);
}); });
@ -686,7 +684,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
// Load all messages // Load all messages
this.primary_message.web_view.add_internal_resources(cid_resources); this.primary_message.add_internal_resources(cid_resources);
yield this.primary_message.load_message_body( yield this.primary_message.load_message_body(
message, this.load_cancellable message, this.load_cancellable
); );
@ -704,7 +702,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
this.config this.config
); );
connect_message_view_signals(attached_message); connect_message_view_signals(attached_message);
attached_message.web_view.add_internal_resources(cid_resources); attached_message.add_internal_resources(cid_resources);
this.sub_messages.add(attached_message); this.sub_messages.add(attached_message);
this._attached_messages.add(attached_message); this._attached_messages.add(attached_message);
attached_message.load_contacts.begin(this.load_cancellable); attached_message.load_contacts.begin(this.load_cancellable);
@ -886,8 +884,8 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
Geary.Memory.Buffer? content) { Geary.Memory.Buffer? content) {
var main = get_toplevel() as Application.MainWindow; var main = get_toplevel() as Application.MainWindow;
if (main != null) { if (main != null) {
if (uri.has_prefix(ClientWebView.CID_URL_PREFIX)) { if (uri.has_prefix(Components.WebView.CID_URL_PREFIX)) {
string cid = uri.substring(ClientWebView.CID_URL_PREFIX.length); string cid = uri.substring(Components.WebView.CID_URL_PREFIX.length);
try { try {
Geary.Attachment attachment = this.email.get_attachment_by_content_id( Geary.Attachment attachment = this.email.get_attachment_by_content_id(
cid cid
@ -934,7 +932,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
private void on_content_loaded() { private void on_content_loaded() {
bool all_loaded = true; bool all_loaded = true;
foreach (ConversationMessage message in this) { foreach (ConversationMessage message in this) {
if (!message.web_view.is_content_loaded) { if (!message.is_content_loaded) {
all_loaded = false; all_loaded = false;
break; break;
} }

View file

@ -957,7 +957,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
*/ */
public void zoom_in() { public void zoom_in() {
message_view_iterator().foreach((msg_view) => { message_view_iterator().foreach((msg_view) => {
msg_view.web_view.zoom_in(); msg_view.zoom_in();
return true; return true;
}); });
} }
@ -967,7 +967,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
*/ */
public void zoom_out() { public void zoom_out() {
message_view_iterator().foreach((msg_view) => { message_view_iterator().foreach((msg_view) => {
msg_view.web_view.zoom_out(); msg_view.zoom_out();
return true; return true;
}); });
} }
@ -977,7 +977,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
*/ */
public void zoom_reset() { public void zoom_reset() {
message_view_iterator().foreach((msg_view) => { message_view_iterator().foreach((msg_view) => {
msg_view.web_view.zoom_reset(); msg_view.zoom_reset();
return true; return true;
}); });
} }
@ -1182,8 +1182,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
row.get_allocation(out alloc); row.get_allocation(out alloc);
int x = 0, y = 0; int x = 0, y = 0;
ConversationWebView web_view = row.view.primary_message.web_view; row.view.primary_message.web_view_translate_coordinates(row, x, anchor_y, out x, out y);
web_view.translate_coordinates(row, x, anchor_y, out x, out y);
Gtk.Adjustment adj = get_adjustment(); Gtk.Adjustment adj = get_adjustment();
y = alloc.y + y; y = alloc.y + y;
@ -1216,14 +1215,13 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
ConversationMessage conversation_message = view.primary_message; ConversationMessage conversation_message = view.primary_message;
int body_top = 0; int body_top = 0;
int body_left = 0; int body_left = 0;
ConversationWebView web_view = conversation_message.web_view; conversation_message.web_view_translate_coordinates(
web_view.translate_coordinates(
this, this,
0, 0, 0, 0,
out body_left, out body_top out body_left, out body_top
); );
int body_height = web_view.get_allocated_height(); int body_height = conversation_message.web_view_get_allocated_height();
int body_bottom = body_top + body_height; int body_bottom = body_top + body_height;
// Only mark the email as read if it's actually visible // Only mark the email as read if it's actually visible

View file

@ -310,8 +310,19 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
[GtkChild] [GtkChild]
internal Components.InfoBarStack info_bars; internal Components.InfoBarStack info_bars;
/**
* Emitted when web_view's content has finished loaded.
*
* See {@link Components.WebView.is_content_loaded} for details.
*/
internal bool is_content_loaded {
get {
return this.web_view != null && this.web_view.is_content_loaded;
}
}
/** HTML view that displays the message body. */ /** HTML view that displays the message body. */
internal ConversationWebView web_view { get; private set; } private ConversationWebView? web_view { get; private set; }
// The message headers represented by this view // The message headers represented by this view
private Geary.EmailHeaderSet headers; private Geary.EmailHeaderSet headers;
@ -426,6 +437,19 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
string uri, string? alt_text, Geary.Memory.Buffer? buffer string uri, string? alt_text, Geary.Memory.Buffer? buffer
); );
/** Emitted when web_view has loaded a resource added to it. */
public signal void internal_resource_loaded(string name);
/** Emitted when web_view's selection has changed. */
public signal void selection_changed(bool has_selection);
/**
* Emitted when web_view's content has finished loaded.
*
* See {@link Components.WebView.is_content_loaded} for details.
*/
public signal void content_loaded();
/** /**
* Constructs a new view from an email's headers and body. * Constructs a new view from an email's headers and body.
@ -467,6 +491,18 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
); );
} }
private void trigger_internal_resource_loaded(string name) {
internal_resource_loaded(name);
}
private void trigger_content_loaded() {
content_loaded();
}
private void trigger_selection_changed(bool has_selection) {
selection_changed(has_selection);
}
private ConversationMessage(Geary.EmailHeaderSet headers, private ConversationMessage(Geary.EmailHeaderSet headers,
string? preview, string? preview,
bool load_remote_resources, bool load_remote_resources,
@ -487,19 +523,10 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
.activate.connect(on_copy_email_address); .activate.connect(on_copy_email_address);
add_action(ACTION_COPY_LINK, true, VariantType.STRING) add_action(ACTION_COPY_LINK, true, VariantType.STRING)
.activate.connect(on_copy_link); .activate.connect(on_copy_link);
add_action(ACTION_COPY_SELECTION, false).activate.connect(() => {
web_view.copy_clipboard();
});
add_action(ACTION_OPEN_INSPECTOR, config.enable_inspector).activate.connect(() => {
this.web_view.get_inspector().show();
});
add_action(ACTION_OPEN_LINK, true, VariantType.STRING) add_action(ACTION_OPEN_LINK, true, VariantType.STRING)
.activate.connect(on_link_activated); .activate.connect(on_link_activated);
add_action(ACTION_SAVE_IMAGE, true, new VariantType("(sms)")) add_action(ACTION_SAVE_IMAGE, true, new VariantType("(sms)"))
.activate.connect(on_save_image); .activate.connect(on_save_image);
add_action(ACTION_SELECT_ALL, true).activate.connect(() => {
web_view.select_all();
});
insert_action_group("msg", message_actions); insert_action_group("msg", message_actions);
// Context menu // Context menu
@ -552,25 +579,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
this.subject_searchable = headers.subject.value.casefold(); this.subject_searchable = headers.subject.value.casefold();
} }
// Web view
this.web_view = new ConversationWebView(config);
this.web_view.context_menu.connect(on_context_menu);
this.web_view.deceptive_link_clicked.connect(on_deceptive_link_clicked);
this.web_view.link_activated.connect((link) => {
on_link_activated(new GLib.Variant("s", link));
});
this.web_view.mouse_target_changed.connect(on_mouse_target_changed);
this.web_view.notify["is-loading"].connect(on_is_loading_notify);
this.web_view.resource_load_started.connect(on_resource_load_started);
this.web_view.remote_image_load_blocked.connect(on_remote_images_blocked);
this.web_view.selection_changed.connect(on_selection_changed);
this.web_view.set_hexpand(true);
this.web_view.set_vexpand(true);
this.web_view.show();
this.body_container.set_has_tooltip(true); // Used to show link URLs this.body_container.set_has_tooltip(true); // Used to show link URLs
this.body_container.add(this.web_view);
this.show_progress_timeout = new Geary.TimeoutManager.milliseconds( this.show_progress_timeout = new Geary.TimeoutManager.milliseconds(
Util.Gtk.SHOW_PROGRESS_TIMEOUT_MSEC, this.on_show_progress_timeout Util.Gtk.SHOW_PROGRESS_TIMEOUT_MSEC, this.on_show_progress_timeout
); );
@ -584,6 +593,51 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
this.progress_pulse.repetition = FOREVER; this.progress_pulse.repetition = FOREVER;
} }
private void initialize_web_view() {
var viewer = get_ancestor(typeof(ConversationViewer)) as ConversationViewer;
// Ensure we share the same WebProcess with the last one
// constructed if possible.
if (viewer != null && viewer.previous_web_view != null) {
this.web_view = new ConversationWebView.with_related_view(
this.config,
viewer.previous_web_view
);
} else {
this.web_view = new ConversationWebView(this.config);
}
if (viewer != null) {
viewer.previous_web_view = this.web_view;
}
this.web_view.context_menu.connect(on_context_menu);
this.web_view.deceptive_link_clicked.connect(on_deceptive_link_clicked);
this.web_view.link_activated.connect((link) => {
on_link_activated(new GLib.Variant("s", link));
});
this.web_view.mouse_target_changed.connect(on_mouse_target_changed);
this.web_view.notify["is-loading"].connect(on_is_loading_notify);
this.web_view.resource_load_started.connect(on_resource_load_started);
this.web_view.remote_image_load_blocked.connect(on_remote_images_blocked);
this.web_view.selection_changed.connect(on_selection_changed);
this.web_view.internal_resource_loaded.connect(trigger_internal_resource_loaded);
this.web_view.content_loaded.connect(trigger_content_loaded);
this.web_view.selection_changed.connect(trigger_selection_changed);
this.web_view.set_hexpand(true);
this.web_view.set_vexpand(true);
this.web_view.show();
this.body_container.add(this.web_view);
add_action(ACTION_COPY_SELECTION, false).activate.connect(() => {
web_view.copy_clipboard();
});
add_action(ACTION_OPEN_INSPECTOR, config.enable_inspector).activate.connect(() => {
this.web_view.get_inspector().show();
});
add_action(ACTION_SELECT_ALL, true).activate.connect(() => {
web_view.select_all();
});
}
~ConversationMessage() { ~ConversationMessage() {
base_unref(); base_unref();
} }
@ -597,10 +651,77 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
base.destroy(); base.destroy();
} }
public async string? get_selection_for_quoting() throws Error {
if (this.web_view == null)
initialize_web_view();
return yield web_view.get_selection_for_quoting();
}
public async string? get_selection_for_find() throws Error {
if (this.web_view == null)
initialize_web_view();
return yield web_view.get_selection_for_find();
}
/**
* Adds a set of internal resources to web_view.
*
* @see add_internal_resource
*/
public void add_internal_resources(Gee.Map<string,Geary.Memory.Buffer> res) {
if (this.web_view == null)
initialize_web_view();
web_view.add_internal_resources(res);
}
public WebKit.PrintOperation new_print_operation() {
if (this.web_view == null)
initialize_web_view();
return new WebKit.PrintOperation(web_view);
}
public async void run_javascript (string script, Cancellable? cancellable) throws Error {
if (this.web_view == null)
initialize_web_view();
yield web_view.run_javascript(script, cancellable);
}
public void zoom_in() {
if (this.web_view == null)
initialize_web_view();
web_view.zoom_in();
}
public void zoom_out() {
if (this.web_view == null)
initialize_web_view();
web_view.zoom_out();
}
public void zoom_reset() {
if (this.web_view == null)
initialize_web_view();
web_view.zoom_reset();
}
public void web_view_translate_coordinates(Gtk.Widget widget, int x, int anchor_y, out int x1, out int y1) {
if (this.web_view == null)
initialize_web_view();
web_view.translate_coordinates(widget, x, anchor_y, out x1, out y1);
}
public int web_view_get_allocated_height() {
if (this.web_view == null)
initialize_web_view();
return web_view.get_allocated_height();
}
/** /**
* Shows the complete message and hides the compact headers. * Shows the complete message and hides the compact headers.
*/ */
public void show_message_body(bool include_transitions=true) { public void show_message_body(bool include_transitions=true) {
if (this.web_view == null)
initialize_web_view();
set_revealer(this.compact_revealer, false, include_transitions); set_revealer(this.compact_revealer, false, include_transitions);
set_revealer(this.header_revealer, true, include_transitions); set_revealer(this.header_revealer, true, include_transitions);
set_revealer(this.body_revealer, true, include_transitions); set_revealer(this.body_revealer, true, include_transitions);
@ -785,6 +906,10 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
throw new GLib.IOError.CANCELLED("Conversation load cancelled"); throw new GLib.IOError.CANCELLED("Conversation load cancelled");
} }
if (this.web_view == null) {
initialize_web_view();
}
bool contact_load_images = ( bool contact_load_images = (
this.primary_contact != null && this.primary_contact != null &&
this.primary_contact.load_remote_resources this.primary_contact.load_remote_resources
@ -835,6 +960,8 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
} }
} }
if (this.web_view == null)
initialize_web_view();
uint webkit_found = yield this.web_view.highlight_search_terms( uint webkit_found = yield this.web_view.highlight_search_terms(
search_matches, cancellable search_matches, cancellable
); );
@ -848,7 +975,9 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
foreach (ContactFlowBoxChild address in this.searchable_addresses) { foreach (ContactFlowBoxChild address in this.searchable_addresses) {
address.unmark_search_terms(); address.unmark_search_terms();
} }
this.web_view.unmark_search_terms();
if (this.web_view != null)
this.web_view.unmark_search_terms();
} }
/** /**
@ -1011,6 +1140,8 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
// returns HTML that is placed into the document in the position // returns HTML that is placed into the document in the position
// where the MIME part was found // where the MIME part was found
private string? inline_image_replacer(Geary.RFC822.Part part) { private string? inline_image_replacer(Geary.RFC822.Part part) {
if (this.web_view == null)
initialize_web_view();
Geary.Mime.ContentType content_type = part.content_type; Geary.Mime.ContentType content_type = part.content_type;
if (content_type.media_type != "image" || if (content_type.media_type != "image" ||
!this.web_view.can_show_mime_type(content_type.to_string())) { !this.web_view.can_show_mime_type(content_type.to_string())) {
@ -1045,7 +1176,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
return "<img alt=\"%s\" class=\"%s\" src=\"%s%s\" />".printf( return "<img alt=\"%s\" class=\"%s\" src=\"%s%s\" />".printf(
clean_filename, clean_filename,
REPLACED_IMAGE_CLASS, REPLACED_IMAGE_CLASS,
ClientWebView.CID_URL_PREFIX, Components.WebView.CID_URL_PREFIX,
Geary.HTML.escape_markup(id) Geary.HTML.escape_markup(id)
); );
} }
@ -1059,7 +1190,9 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
this.load_remote_resources = true; this.load_remote_resources = true;
this.remote_resources_requested = 0; this.remote_resources_requested = 0;
this.remote_resources_loaded = 0; this.remote_resources_loaded = 0;
this.web_view.load_remote_images(); if (this.web_view != null) {
this.web_view.load_remote_images();
}
if (update_email_flag) { if (update_email_flag) {
flag_remote_images(); flag_remote_images();
} }
@ -1074,11 +1207,13 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
if (placeholder != null) { if (placeholder != null) {
this.body_placeholder = placeholder; this.body_placeholder = placeholder;
this.web_view.hide(); if (this.web_view != null)
this.web_view.hide();
this.body_container.add(placeholder); this.body_container.add(placeholder);
show_message_body(true); show_message_body(true);
} else { } else {
this.web_view.show(); if (this.web_view != null)
this.web_view.show();
} }
} }
@ -1106,10 +1241,12 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
} }
private void on_is_loading_notify() { private void on_is_loading_notify() {
if (this.web_view.is_loading) { if (this.web_view != null) {
start_progress_loading(); if (this.web_view.is_loading) {
} else { start_progress_loading();
stop_progress_loading(); } else {
stop_progress_loading();
}
} }
} }
@ -1369,7 +1506,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
alt_text = (string) alt_maybe; alt_text = (string) alt_maybe;
} }
if (uri.has_prefix(ClientWebView.CID_URL_PREFIX)) { if (uri.has_prefix(Components.WebView.CID_URL_PREFIX)) {
// We can get the data directly from the attachment, so // We can get the data directly from the attachment, so
// don't bother getting it from the web view // don't bother getting it from the web view
save_image(uri, alt_text, null); save_image(uri, alt_text, null);

View file

@ -24,6 +24,14 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
get; private set; default = null; get; private set; default = null;
} }
/**
* The most recent web view created in this viewer.
*
* Keep the last created web view around so others can share the
* same WebKitGTK WebProcess.
*/
internal ConversationWebView? previous_web_view { get; set; default = null; }
private Application.Configuration config; private Application.Configuration config;
private Gee.Set<Geary.App.Conversation>? selection_while_composing = null; private Gee.Set<Geary.App.Conversation>? selection_while_composing = null;
@ -251,7 +259,10 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
Application.ContactStore contacts, Application.ContactStore contacts,
bool start_mark_timer) bool start_mark_timer)
throws GLib.Error { throws GLib.Error {
remove_current_list(); // Keep the old ScrolledWindow around long enough for its
// descendant web views to be kept so their WebProcess can be
// re-used.
var old_scroller = remove_current_list();
ConversationListBox new_list = new ConversationListBox( ConversationListBox new_list = new ConversationListBox(
conversation, conversation,
@ -297,6 +308,9 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
} }
yield new_list.load_conversation(scroll_to, query); yield new_list.load_conversation(scroll_to, query);
// Not strictly necessary, but keeps the compiler happy
old_scroller.destroy();
} }
// Add a new conversation list to the UI // Add a new conversation list to the UI
@ -316,7 +330,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
} }
// Remove any existing conversation list, cancelling its loading // Remove any existing conversation list, cancelling its loading
private void remove_current_list() { private Gtk.ScrolledWindow remove_current_list() {
if (this.find_cancellable != null) { if (this.find_cancellable != null) {
this.find_cancellable.cancel(); this.find_cancellable.cancel();
this.find_cancellable = null; this.find_cancellable = null;
@ -328,15 +342,17 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
this.current_list = null; this.current_list = null;
} }
var old_scroller = this.conversation_scroller;
// XXX GTK+ Bug 778190 workaround // XXX GTK+ Bug 778190 workaround
this.conversation_scroller.destroy(); // removes the list this.conversation_page.remove(old_scroller);
new_conversation_scroller(); new_conversation_scroller();
return old_scroller;
} }
private void new_conversation_scroller() { private void new_conversation_scroller() {
// XXX Work around for GTK+ Bug 778190: Instead of replacing // XXX Work around for GTK+ Bug 778190: Instead of replacing
// the Viewport that contains the current list, replace the // the Viewport that contains the current list, replace the
// complete ScrolledWindow. Need to put remove this method and // complete ScrolledWindow. Need to remove this method and
// put the settings back into conversation-viewer.ui when we // put the settings back into conversation-viewer.ui when we
// can rely on it being fixed again. // can rely on it being fixed again.
Gtk.ScrolledWindow scroller = new Gtk.ScrolledWindow(null, null); Gtk.ScrolledWindow scroller = new Gtk.ScrolledWindow(null, null);

View file

@ -6,7 +6,7 @@
* (version 2.1 or later). See the COPYING file in this distribution. * (version 2.1 or later). See the COPYING file in this distribution.
*/ */
public class ConversationWebView : ClientWebView { public class ConversationWebView : Components.WebView {
private const string DECEPTIVE_LINK_CLICKED = "deceptiveLinkClicked"; private const string DECEPTIVE_LINK_CLICKED = "deceptiveLinkClicked";
@ -41,10 +41,10 @@ public class ConversationWebView : ClientWebView {
public static new void load_resources() public static new void load_resources()
throws Error { throws Error {
ConversationWebView.app_script = ClientWebView.load_app_script( ConversationWebView.app_script = Components.WebView.load_app_script(
"conversation-web-view.js" "conversation-web-view.js"
); );
ConversationWebView.app_stylesheet = ClientWebView.load_app_stylesheet( ConversationWebView.app_stylesheet = Components.WebView.load_app_stylesheet(
"conversation-web-view.css" "conversation-web-view.css"
); );
} }
@ -56,16 +56,33 @@ public class ConversationWebView : ClientWebView {
); );
/**
* Constructs a new web view for displaying an email message body.
*
* A new WebKitGTK WebProcess will be constructed for this view.
*/
public ConversationWebView(Application.Configuration config) { public ConversationWebView(Application.Configuration config) {
base(config); base(config);
init();
// These only need to be added when creating a new WebProcess,
// not when sharing one
this.user_content_manager.add_script(ConversationWebView.app_script); this.user_content_manager.add_script(ConversationWebView.app_script);
this.user_content_manager.add_style_sheet(ConversationWebView.app_stylesheet); this.user_content_manager.add_style_sheet(ConversationWebView.app_stylesheet);
}
register_message_handler( /**
DECEPTIVE_LINK_CLICKED, on_deceptive_link_clicked * Constructs a new web view for displaying an email message body.
); *
* The WebKitGTK WebProcess will be shared with the related view's
this.notify["preferred-height"].connect(() => queue_resize()); * process.
*/
internal ConversationWebView.with_related_view(
Application.Configuration config,
ConversationWebView related
) {
base.with_related_view(config, related);
init();
} }
/** /**
@ -206,6 +223,14 @@ public class ConversationWebView : ClientWebView {
minimum_height = natural_height = 0; minimum_height = natural_height = 0;
} }
private void init() {
register_message_handler(
DECEPTIVE_LINK_CLICKED, on_deceptive_link_clicked
);
this.notify["preferred-height"].connect(() => queue_resize());
}
private void on_deceptive_link_clicked(WebKit.JavascriptResult result) { private void on_deceptive_link_clicked(WebKit.JavascriptResult result) {
try { try {
JSC.Value object = result.get_js_value(); JSC.Value object = result.get_js_value();

View file

@ -46,7 +46,6 @@ client_vala_sources = files(
'client-action.vala', 'client-action.vala',
'components/client-web-view.vala',
'components/components-attachment-pane.vala', 'components/components-attachment-pane.vala',
'components/components-conversation-actions.vala', 'components/components-conversation-actions.vala',
'components/components-conversation-action-bar.vala', 'components/components-conversation-action-bar.vala',
@ -64,6 +63,7 @@ client_vala_sources = files(
'components/components-reflow-box.c', 'components/components-reflow-box.c',
'components/components-search-bar.vala', 'components/components-search-bar.vala',
'components/components-validator.vala', 'components/components-validator.vala',
'components/components-web-view.vala',
'components/count-badge.vala', 'components/count-badge.vala',
'components/folder-popover.vala', 'components/folder-popover.vala',
'components/icon-factory.vala', 'components/icon-factory.vala',
@ -191,16 +191,6 @@ client_vala_args += [
) )
] ]
# Enable shared shecondary process if available.
# See issues #558 and #559
webkit_version = webkit2gtk.version().split('.')
if webkit_version[0].to_int() <= 2 and webkit_version[1].to_int() <= 24
message('Enabling WebKitGTK shared process model')
client_vala_args += [
'-D', 'HAS_WEBKIT_SHARED_PROC'
]
endif
# Main client application library # Main client application library
client_lib = shared_library( client_lib = shared_library(
client_package, client_package,

View file

@ -32,22 +32,14 @@ public class GearyWebExtension : Object {
private const string[] ALLOWED_SCHEMES = { "cid", "geary", "data", "blob" }; private const string[] ALLOWED_SCHEMES = { "cid", "geary", "data", "blob" };
private const string REMOTE_LOAD_VAR = "_gearyAllowRemoteResourceLoads";
private WebKit.WebExtension extension; private WebKit.WebExtension extension;
public GearyWebExtension(WebKit.WebExtension extension) { public GearyWebExtension(WebKit.WebExtension extension) {
this.extension = extension; this.extension = extension;
extension.page_created.connect((extension, web_page) => { extension.page_created.connect(on_page_created);
web_page.console_message_sent.connect(on_console_message);
web_page.send_request.connect(on_send_request);
// XXX investigate whether the earliest supported
// version of WK supports the DOM "selectionchanged"
// event, and if so use that rather that doing it in
// here in the extension
web_page.get_editor().selection_changed.connect(() => {
selection_changed(web_page);
});
});
} }
// XXX Conditionally enable while we still depend on WK2 <2.12 // XXX Conditionally enable while we still depend on WK2 <2.12
@ -89,14 +81,7 @@ public class GearyWebExtension : Object {
WebKit.Frame frame = page.get_main_frame(); WebKit.Frame frame = page.get_main_frame();
JSC.Context context = frame.get_js_context(); JSC.Context context = frame.get_js_context();
try { try {
JSC.Value ret = execute_script( should_load = Util.JS.to_bool(context.get_value(REMOTE_LOAD_VAR));
context,
"geary.allowRemoteImages",
GLib.Log.FILE,
GLib.Log.METHOD,
GLib.Log.LINE
);
should_load = Util.JS.to_bool(ret);
} catch (GLib.Error err) { } catch (GLib.Error err) {
debug( debug(
"Error checking PageState::allowRemoteImages: %s", "Error checking PageState::allowRemoteImages: %s",
@ -154,4 +139,24 @@ public class GearyWebExtension : Object {
return ret; return ret;
} }
private void on_page_created(WebKit.WebExtension extension,
WebKit.WebPage page) {
WebKit.Frame frame = page.get_main_frame();
JSC.Context context = frame.get_js_context();
context.set_value(
REMOTE_LOAD_VAR,
new JSC.Value.boolean(context, false)
);
page.console_message_sent.connect(on_console_message);
page.send_request.connect(on_send_request);
// XXX investigate whether the earliest supported
// version of WK supports the DOM "selectionchanged"
// event, and if so use that rather that doing it in
// here in the extension
page.get_editor().selection_changed.connect(() => {
selection_changed(page);
});
}
} }

View file

@ -6,35 +6,42 @@
*/ */
public abstract class ClientWebViewTestCase<V> : TestCase { public abstract class Components.WebViewTestCase<V> : TestCase {
protected V? test_view = null; protected V? test_view = null;
protected Application.Configuration? config = null; protected Application.Configuration? config = null;
protected ClientWebViewTestCase(string name) { protected WebViewTestCase(string name) {
base(name); base(name);
}
public override void set_up() {
this.config = new Application.Configuration(Application.Client.SCHEMA_ID); this.config = new Application.Configuration(Application.Client.SCHEMA_ID);
this.config.enable_debug = true; this.config.enable_debug = true;
ClientWebView.init_web_context(
WebView.init_web_context(
this.config, this.config,
File.new_for_path(_BUILD_ROOT_DIR).get_child("src"), File.new_for_path(_BUILD_ROOT_DIR).get_child("src"),
File.new_for_path("/tmp") // XXX use something better here File.new_for_path("/tmp") // XXX use something better here
); );
try { try {
ClientWebView.load_resources(GLib.File.new_for_path("/tmp")); WebView.load_resources(GLib.File.new_for_path("/tmp"));
} catch (GLib.Error err) { } catch (GLib.Error err) {
GLib.assert_not_reached(); GLib.assert_not_reached();
} }
this.test_view = set_up_test_view();
} }
public override void set_up() { protected override void tear_down() {
this.test_view = set_up_test_view(); this.config = null;
this.test_view = null;
} }
protected abstract V set_up_test_view(); protected abstract V set_up_test_view();
protected virtual void load_body_fixture(string html = "") { protected virtual void load_body_fixture(string html = "") {
ClientWebView client_view = (ClientWebView) this.test_view; WebView client_view = (WebView) this.test_view;
client_view.load_html(html); client_view.load_html(html);
while (!client_view.is_content_loaded) { while (!client_view.is_content_loaded) {
Gtk.main_iteration(); Gtk.main_iteration();
@ -42,7 +49,7 @@ public abstract class ClientWebViewTestCase<V> : TestCase {
} }
protected WebKit.JavascriptResult run_javascript(string command) throws Error { protected WebKit.JavascriptResult run_javascript(string command) throws Error {
ClientWebView view = (ClientWebView) this.test_view; WebView view = (WebView) this.test_view;
view.run_javascript.begin(command, null, this.async_completion); view.run_javascript.begin(command, null, this.async_completion);
return view.run_javascript.end(async_result()); return view.run_javascript.end(async_result());
} }

View file

@ -5,10 +5,10 @@
* (version 2.1 or later). See the COPYING file in this distribution. * (version 2.1 or later). See the COPYING file in this distribution.
*/ */
public class ClientWebViewTest : TestCase { public class Components.WebViewTest : TestCase {
public ClientWebViewTest() { public WebViewTest() {
base("ClientWebViewTest"); base("Components.WebViewTest");
add_test("init_web_context", init_web_context); add_test("init_web_context", init_web_context);
add_test("load_resources", load_resources); add_test("load_resources", load_resources);
} }
@ -18,7 +18,7 @@ public class ClientWebViewTest : TestCase {
Application.Client.SCHEMA_ID Application.Client.SCHEMA_ID
); );
config.enable_debug = true; config.enable_debug = true;
ClientWebView.init_web_context( WebView.init_web_context(
config, config,
File.new_for_path(_BUILD_ROOT_DIR).get_child("src"), File.new_for_path(_BUILD_ROOT_DIR).get_child("src"),
File.new_for_path("/tmp") // XXX use something better here File.new_for_path("/tmp") // XXX use something better here
@ -27,7 +27,7 @@ public class ClientWebViewTest : TestCase {
public void load_resources() throws GLib.Error { public void load_resources() throws GLib.Error {
try { try {
ClientWebView.load_resources(GLib.File.new_for_path("/tmp")); WebView.load_resources(GLib.File.new_for_path("/tmp"));
} catch (GLib.Error err) { } catch (GLib.Error err) {
assert_not_reached(); assert_not_reached();
} }

View file

@ -5,7 +5,7 @@
* (version 2.1 or later). See the COPYING file in this distribution. * (version 2.1 or later). See the COPYING file in this distribution.
*/ */
public class Composer.WebViewTest : ClientWebViewTestCase<Composer.WebView> { public class Composer.WebViewTest : Components.WebViewTestCase<Composer.WebView> {
public WebViewTest() { public WebViewTest() {

View file

@ -5,24 +5,24 @@
* (version 2.1 or later). See the COPYING file in this distribution. * (version 2.1 or later). See the COPYING file in this distribution.
*/ */
class ClientPageStateTest : ClientWebViewTestCase<ClientWebView> { class Components.PageStateTest : WebViewTestCase<WebView> {
private class TestClientWebView : ClientWebView { private class TestWebView : Components.WebView {
public TestClientWebView(Application.Configuration config) { public TestWebView(Application.Configuration config) {
base(config); base(config);
} }
} }
public ClientPageStateTest() { public PageStateTest() {
base("ClientPageStateTest"); base("Components.PageStateTest");
add_test("content_loaded", content_loaded); add_test("content_loaded", content_loaded);
try { try {
ClientWebView.load_resources(GLib.File.new_for_path("/tmp")); WebView.load_resources(GLib.File.new_for_path("/tmp"));
} catch (GLib.Error err) { } catch (GLib.Error err) {
GLib.assert_not_reached(); GLib.assert_not_reached();
} }
@ -45,7 +45,7 @@ class ClientPageStateTest : ClientWebViewTestCase<ClientWebView> {
assert(content_loaded_triggered); assert(content_loaded_triggered);
} }
protected override ClientWebView set_up_test_view() { protected override WebView set_up_test_view() {
WebKit.UserScript test_script; WebKit.UserScript test_script;
test_script = new WebKit.UserScript( test_script = new WebKit.UserScript(
"var geary = new PageState()", "var geary = new PageState()",
@ -55,7 +55,7 @@ class ClientPageStateTest : ClientWebViewTestCase<ClientWebView> {
null null
); );
ClientWebView view = new TestClientWebView(this.config); WebView view = new TestWebView(this.config);
view.get_user_content_manager().add_script(test_script); view.get_user_content_manager().add_script(test_script);
return view; return view;
} }

View file

@ -5,7 +5,7 @@
* (version 2.1 or later). See the COPYING file in this distribution. * (version 2.1 or later). See the COPYING file in this distribution.
*/ */
class Composer.PageStateTest : ClientWebViewTestCase<Composer.WebView> { class Composer.PageStateTest : Components.WebViewTestCase<Composer.WebView> {
public const string COMPLETE_BODY_TEMPLATE = public const string COMPLETE_BODY_TEMPLATE =
"""<div id="geary-body" dir="auto">%s<div><br></div><div><br></div></div><div id="geary-signature" dir="auto"></div>"""; """<div id="geary-body" dir="auto">%s<div><br></div><div><br></div></div><div id="geary-signature" dir="auto"></div>""";

View file

@ -5,7 +5,7 @@
* (version 2.1 or later). See the COPYING file in this distribution. * (version 2.1 or later). See the COPYING file in this distribution.
*/ */
class ConversationPageStateTest : ClientWebViewTestCase<ConversationWebView> { class ConversationPageStateTest : Components.WebViewTestCase<ConversationWebView> {
public ConversationPageStateTest() { public ConversationPageStateTest() {
base("ConversationPageStateTest"); base("ConversationPageStateTest");

View file

@ -82,9 +82,9 @@ test_client_sources = [
'client/application/application-certificate-manager-test.vala', 'client/application/application-certificate-manager-test.vala',
'client/application/application-client-test.vala', 'client/application/application-client-test.vala',
'client/application/application-configuration-test.vala', 'client/application/application-configuration-test.vala',
'client/components/client-web-view-test.vala',
'client/components/client-web-view-test-case.vala',
'client/components/components-validator-test.vala', 'client/components/components-validator-test.vala',
'client/components/components-web-view-test-case.vala',
'client/components/components-web-view-test.vala',
'client/composer/composer-web-view-test.vala', 'client/composer/composer-web-view-test.vala',
'client/composer/composer-widget-test.vala', 'client/composer/composer-widget-test.vala',
'client/util/util-avatar-test.vala', 'client/util/util-avatar-test.vala',
@ -92,7 +92,7 @@ test_client_sources = [
'client/util/util-email-test.vala', 'client/util/util-email-test.vala',
'client/util/util-js-test.vala', 'client/util/util-js-test.vala',
'js/client-page-state-test.vala', 'js/components-page-state-test.vala',
'js/composer-page-state-test.vala', 'js/composer-page-state-test.vala',
'js/conversation-page-state-test.vala', 'js/conversation-page-state-test.vala',

View file

@ -53,10 +53,10 @@ int main(string[] args) {
client.add_suite(new Application.CertificateManagerTest().suite); client.add_suite(new Application.CertificateManagerTest().suite);
client.add_suite(new Application.ClientTest().suite); client.add_suite(new Application.ClientTest().suite);
client.add_suite(new Application.ConfigurationTest().suite); client.add_suite(new Application.ConfigurationTest().suite);
client.add_suite(new ClientWebViewTest().suite); client.add_suite(new Components.WebViewTest().suite);
client.add_suite(new Components.ValidatorTest().suite);
client.add_suite(new Composer.WebViewTest().suite); client.add_suite(new Composer.WebViewTest().suite);
client.add_suite(new Composer.WidgetTest().suite); client.add_suite(new Composer.WidgetTest().suite);
client.add_suite(new Components.ValidatorTest().suite);
client.add_suite(new Util.Avatar.Test().suite); client.add_suite(new Util.Avatar.Test().suite);
client.add_suite(new Util.Cache.Test().suite); client.add_suite(new Util.Cache.Test().suite);
client.add_suite(new Util.Email.Test().suite); client.add_suite(new Util.Email.Test().suite);
@ -64,7 +64,7 @@ int main(string[] args) {
TestSuite js = new TestSuite("js"); TestSuite js = new TestSuite("js");
js.add_suite(new ClientPageStateTest().suite); js.add_suite(new Components.PageStateTest().suite);
js.add_suite(new Composer.PageStateTest().suite); js.add_suite(new Composer.PageStateTest().suite);
js.add_suite(new ConversationPageStateTest().suite); js.add_suite(new ConversationPageStateTest().suite);

View file

@ -1,11 +0,0 @@
/*
* Copyright 2016 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* Enables remote image loading in a client web view.
*/
geary.allowRemoteImages = true;

View file

@ -6,7 +6,7 @@
*/ */
/** /**
* Application logic for ClientWebView and subclasses. * Application logic for Components.WebView and subclasses.
*/ */
let PageState = function() { let PageState = function() {
@ -14,7 +14,6 @@ let PageState = function() {
}; };
PageState.prototype = { PageState.prototype = {
init: function() { init: function() {
this.allowRemoteImages = false;
this.isLoaded = false; this.isLoaded = false;
this.undoEnabled = false; this.undoEnabled = false;
this.redoEnabled = false; this.redoEnabled = false;
@ -108,7 +107,7 @@ PageState.prototype = {
window.webkit.messageHandlers.contentLoaded.postMessage(null); window.webkit.messageHandlers.contentLoaded.postMessage(null);
}, },
loadRemoteImages: function() { loadRemoteImages: function() {
this.allowRemoteImages = true; window._gearyAllowRemoteResourceLoads = true;
let images = document.getElementsByTagName("IMG"); let images = document.getElementsByTagName("IMG");
for (let i = 0; i < images.length; i++) { for (let i = 0; i < images.length; i++) {
let img = images.item(i); let img = images.item(i);

View file

@ -473,6 +473,7 @@
<object class="GtkRevealer" id="body_revealer"> <object class="GtkRevealer" id="body_revealer">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="transition_type">slide-up</property>
<child> <child>
<object class="GtkGrid"> <object class="GtkGrid">
<property name="visible">True</property> <property name="visible">True</property>

View file

@ -9,8 +9,7 @@
<file compressed="true" preprocess="xml-stripblanks">accounts_editor_servers_pane.ui</file> <file compressed="true" preprocess="xml-stripblanks">accounts_editor_servers_pane.ui</file>
<file compressed="true" preprocess="xml-stripblanks">application-main-window.ui</file> <file compressed="true" preprocess="xml-stripblanks">application-main-window.ui</file>
<file compressed="true" preprocess="xml-stripblanks">certificate_warning_dialog.glade</file> <file compressed="true" preprocess="xml-stripblanks">certificate_warning_dialog.glade</file>
<file compressed="true">client-web-view.js</file> <file compressed="true">components-web-view.js</file>
<file compressed="true">client-web-view-allow-remote-images.js</file>
<file compressed="true" preprocess="xml-stripblanks">components-attachment-pane.ui</file> <file compressed="true" preprocess="xml-stripblanks">components-attachment-pane.ui</file>
<file compressed="true" preprocess="xml-stripblanks">components-attachment-pane-menus.ui</file> <file compressed="true" preprocess="xml-stripblanks">components-attachment-pane-menus.ui</file>
<file compressed="true" preprocess="xml-stripblanks">components-attachment-view.ui</file> <file compressed="true" preprocess="xml-stripblanks">components-attachment-view.ui</file>