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

Revert merge request GNOME/geary!374 for now since the shared process
model breaks old-style WebProcess message handler IPC.

This can be un-reverted when out JS is ported to the new Messages API
that is landing in WebKitGTK 2.28.

This reverts commit e4a5b85698, reversing
changes made to 66f6525480.
This commit is contained in:
Michael James Gratton 2020-02-12 16:48:24 +11:00
parent d9c10b5f20
commit cbe6e0ba9b
28 changed files with 240 additions and 406 deletions

View file

@ -101,7 +101,6 @@
<li>Extend undo for email actions such as archiving, marking</li>
<li>Undo sending, saving and discarding composed email</li>
<li>Undo editing in text fields, including in the composer</li>
<li>Conversation loading performance improvements</li>
<li>App-wide notification preferences now handled by desktop</li>
<li>Improved missing attachment detection in composer</li>
<li>Initial plugin system</li>

View file

@ -31,6 +31,7 @@ src/client/application/goa-mediator.vala
src/client/application/main.vala
src/client/application/secret-mediator.vala
src/client/client-action.vala
src/client/components/client-web-view.vala
src/client/components/components-attachment-pane.vala
src/client/components/components-entry-undo.vala
src/client/components/components-in-app-notification.vala
@ -39,7 +40,6 @@ src/client/components/components-placeholder-pane.vala
src/client/components/components-preferences-window.vala
src/client/components/components-search-bar.vala
src/client/components/components-validator.vala
src/client/components/components-web-view.vala
src/client/components/count-badge.vala
src/client/components/folder-popover.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 {
private Components.WebView signature_view;
private ClientWebView signature_view;
private Geary.AccountInformation account;
private string old_value;
@ -728,7 +728,7 @@ internal class Accounts.SignatureChangedCommand : Application.Command {
private bool new_enabled = false;
public SignatureChangedCommand(Components.WebView signature_view,
public SignatureChangedCommand(ClientWebView signature_view,
Geary.AccountInformation account) {
this.signature_view = signature_view;
this.account = account;

View file

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

View file

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

View file

@ -20,6 +20,12 @@ int main(string[] args) {
Environment.set_variable("G_TLS_GNUTLS_PRIORITY", "NORMAL:%COMPAT:!VERS-SSL3.0", false);
#endif
// 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
// messages as possible
Geary.Logging.init();

View file

@ -1,6 +1,6 @@
/*
* Copyright 2016 Software Freedom Conservancy Inc.
* Copyright 2016-2019 Michael Gratton <mike@vee.net>
* 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.
@ -14,7 +14,7 @@
* integration, Inspector support, and remote and inline image
* handling.
*/
public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface {
/** URI Scheme and delimiter for internal resource loads. */
@ -65,6 +65,7 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
private static WebKit.UserStyleSheet? user_stylesheet = null;
private static WebKit.UserScript? script = null;
private static WebKit.UserScript? allow_remote_images = null;
/**
@ -75,18 +76,23 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
File cache_dir) {
WebsiteDataManager data_manager = new WebsiteDataManager(cache_dir.get_path());
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
// ever shows a single HTML document.
context.set_cache_model(WebKit.CacheModel.DOCUMENT_VIEWER);
context.register_uri_scheme("cid", (req) => {
WebView? view = req.get_web_view() as WebView;
ClientWebView? view = req.get_web_view() as ClientWebView;
if (view != null) {
view.handle_cid_request(req);
}
});
context.register_uri_scheme("geary", (req) => {
WebView? view = req.get_web_view() as WebView;
ClientWebView? view = req.get_web_view() as ClientWebView;
if (view != null) {
view.handle_internal_request(req);
}
@ -107,22 +113,25 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
update_spellcheck(context, config);
});
WebView.default_context = context;
ClientWebView.default_context = context;
}
/**
* Loads static resources used by WebView.
* Loads static resources used by ClientWebView.
*/
public static void load_resources(GLib.File user_dir)
throws GLib.Error {
WebView.script = load_app_script(
"components-web-view.js"
ClientWebView.script = load_app_script(
"client-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 }) {
GLib.File stylesheet = user_dir.get_child(name);
try {
WebView.user_stylesheet = load_user_stylesheet(stylesheet);
ClientWebView.user_stylesheet = load_user_stylesheet(stylesheet);
break;
} catch (GLib.IOError.NOT_FOUND err) {
// All good, try the next one or just exit
@ -290,9 +299,8 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
public signal void remote_image_load_blocked();
protected WebView(Application.Configuration config,
WebKit.UserContentManager? custom_manager = null,
WebView? related = null) {
protected ClientWebView(Application.Configuration config,
WebKit.UserContentManager? custom_manager = null) {
WebKit.Settings setts = new WebKit.Settings();
setts.allow_modal_dialogs = false;
setts.default_charset = "UTF-8";
@ -313,40 +321,62 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
WebKit.UserContentManager content_manager =
custom_manager ?? new WebKit.UserContentManager();
content_manager.add_script(WebView.script);
if (WebView.user_stylesheet != null) {
content_manager.add_style_sheet(WebView.user_stylesheet);
content_manager.add_script(ClientWebView.script);
if (ClientWebView.user_stylesheet != null) {
content_manager.add_style_sheet(ClientWebView.user_stylesheet);
}
Object(
settings: setts,
web_context: ClientWebView.default_context,
user_content_manager: content_manager,
web_context: WebView.default_context
settings: setts
);
base_ref();
init(config);
}
/**
* 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
// 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
);
base_ref();
init(config);
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);
}
~WebView() {
~ClientWebView() {
base_unref();
}
@ -403,7 +433,13 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
* effect.
*/
public void allow_remote_image_loading() {
this.run_javascript.begin("_gearyAllowRemoteResourceLoads = true", null);
// Use a separate script here since we need to update the
// 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);
}
/**
@ -479,7 +515,7 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
JavaScriptMessageHandler handler) {
// XXX can't use the delegate directly, see b.g.o Bug
// 604781. However the workaround below creates a circular
// reference, causing WebView instances to leak. So to
// reference, causing ClientWebView instances to leak. So to
// work around that we need to record handler ids and
// disconnect them when being destroyed.
ulong id = this.user_content_manager.script_message_received[name].connect(
@ -491,50 +527,6 @@ public abstract class Components.WebView : 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) {
if (!handle_internal_response(request)) {
request.finish_error(new FileError.NOENT("Unknown CID"));

View file

@ -9,7 +9,7 @@
/**
* A WebView for editing messages in the composer.
*/
public class Composer.WebView : Components.WebView {
public class Composer.WebView : ClientWebView {
// WebKit message handler names
@ -92,10 +92,10 @@ public class Composer.WebView : Components.WebView {
public static new void load_resources()
throws Error {
WebView.app_style = Components.WebView.load_app_stylesheet(
WebView.app_style = ClientWebView.load_app_stylesheet(
"composer-web-view.css"
);
WebView.app_script = Components.WebView.load_app_script(
WebView.app_script = ClientWebView.load_app_script(
"composer-web-view.js"
);
}

View file

@ -1320,7 +1320,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
email.inline_files.set_all(this.inline_files);
email.cid_files.set_all(this.cid_files);
email.img_src_prefix = Components.WebView.INTERNAL_URL_PREFIX;
email.img_src_prefix = ClientWebView.INTERNAL_URL_PREFIX;
try {
if (!for_draft) {
@ -2077,7 +2077,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
string unique_filename;
add_inline_part(byte_buffer, filename, out unique_filename);
this.editor.insert_image(
Components.WebView.INTERNAL_URL_PREFIX + unique_filename
ClientWebView.INTERNAL_URL_PREFIX + unique_filename
);
throw new Geary.EngineError.UNSUPPORTED("Mock method");
} catch (Error error) {
@ -2807,7 +2807,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
string unique_filename;
add_inline_part(file_buffer, path, out unique_filename);
this.editor.insert_image(
Components.WebView.INTERNAL_URL_PREFIX + unique_filename
ClientWebView.INTERNAL_URL_PREFIX + unique_filename
);
} catch (Error err) {
attachment_failed(err.message);
@ -2906,7 +2906,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
}
this.editor.insert_image(
Components.WebView.INTERNAL_URL_PREFIX + unique_filename
ClientWebView.INTERNAL_URL_PREFIX + unique_filename
);
}

View file

@ -479,7 +479,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
if (this.body_selection_message != null) {
try {
selection =
yield this.body_selection_message.get_selection_for_quoting();
yield this.body_selection_message.web_view.get_selection_for_quoting();
} catch (Error err) {
debug("Failed to get selection for quoting: %s", err.message);
}
@ -495,7 +495,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
if (this.body_selection_message != null) {
try {
selection =
yield this.body_selection_message.get_selection_for_find();
yield this.body_selection_message.web_view.get_selection_for_find();
} catch (Error err) {
debug("Failed to get selection for find: %s", err.message);
}
@ -588,10 +588,12 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
Json.Generator generator = new Json.Generator();
generator.set_root(builder.get_root());
string js = "geary.addPrintHeaders(" + generator.to_data(null) + ");";
yield this.primary_message.run_javascript(js, null);
yield this.primary_message.web_view.run_javascript(js, null);
Gtk.Window? window = get_toplevel() as Gtk.Window;
WebKit.PrintOperation op = this.primary_message.new_print_operation();
WebKit.PrintOperation op = new WebKit.PrintOperation(
this.primary_message.web_view
);
Gtk.PrintSettings settings = new Gtk.PrintSettings();
if (this.email.subject != null) {
@ -618,14 +620,14 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
}
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.internal_link_activated.connect((y) => {
internal_link_activated(y);
});
view.internal_resource_loaded.connect(on_resource_loaded);
view.save_image.connect(on_save_image);
view.selection_changed.connect((has_selection) => {
view.web_view.internal_resource_loaded.connect(on_resource_loaded);
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;
body_selection_changed(has_selection);
});
@ -701,7 +703,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
// Load all messages
this.primary_message.add_internal_resources(cid_resources);
this.primary_message.web_view.add_internal_resources(cid_resources);
yield this.primary_message.load_message_body(
message, this.load_cancellable
);
@ -719,7 +721,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
this.config
);
connect_message_view_signals(attached_message);
attached_message.add_internal_resources(cid_resources);
attached_message.web_view.add_internal_resources(cid_resources);
this.sub_messages.add(attached_message);
this._attached_messages.add(attached_message);
attached_message.load_contacts.begin(this.load_cancellable);
@ -906,8 +908,8 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
Geary.Memory.Buffer? content) {
var main = get_toplevel() as Application.MainWindow;
if (main != null) {
if (uri.has_prefix(Components.WebView.CID_URL_PREFIX)) {
string cid = uri.substring(Components.WebView.CID_URL_PREFIX.length);
if (uri.has_prefix(ClientWebView.CID_URL_PREFIX)) {
string cid = uri.substring(ClientWebView.CID_URL_PREFIX.length);
try {
Geary.Attachment attachment = this.email.get_attachment_by_content_id(
cid
@ -954,7 +956,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
private void on_content_loaded() {
bool all_loaded = true;
foreach (ConversationMessage message in this) {
if (!message.is_content_loaded) {
if (!message.web_view.is_content_loaded) {
all_loaded = false;
break;
}

View file

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

View file

@ -218,19 +218,8 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
[GtkChild]
internal Gtk.Grid infobars;
/**
* 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. */
private ConversationWebView? web_view { get; private set; }
internal ConversationWebView web_view { get; private set; }
// The message headers represented by this view
private Geary.EmailHeaderSet headers;
@ -346,19 +335,6 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
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.
@ -400,18 +376,6 @@ 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,
string? preview,
bool load_remote_resources,
@ -432,10 +396,19 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
.activate.connect(on_copy_email_address);
add_action(ACTION_COPY_LINK, true, VariantType.STRING)
.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)
.activate.connect(on_link_activated);
add_action(ACTION_SAVE_IMAGE, true, new VariantType("(sms)"))
.activate.connect(on_save_image);
add_action(ACTION_SELECT_ALL, true).activate.connect(() => {
web_view.select_all();
});
insert_action_group("msg", message_actions);
// Context menu
@ -488,37 +461,9 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
this.subject_searchable = headers.subject.value.casefold();
}
this.body_container.set_has_tooltip(true); // Used to show link URLs
this.show_progress_timeout = new Geary.TimeoutManager.milliseconds(
Util.Gtk.SHOW_PROGRESS_TIMEOUT_MSEC, this.on_show_progress_timeout
);
this.hide_progress_timeout = new Geary.TimeoutManager.milliseconds(
Util.Gtk.HIDE_PROGRESS_TIMEOUT_MSEC, this.on_hide_progress_timeout
);
this.progress_pulse = new Geary.TimeoutManager.milliseconds(
Util.Gtk.PROGRESS_PULSE_TIMEOUT_MSEC, this.body_progress.pulse
);
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;
}
// 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) => {
@ -531,22 +476,23 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
this.remote_images_infobar.show();
});
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.set_has_tooltip(true); // Used to show link URLs
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();
});
this.show_progress_timeout = new Geary.TimeoutManager.milliseconds(
Util.Gtk.SHOW_PROGRESS_TIMEOUT_MSEC, this.on_show_progress_timeout
);
this.hide_progress_timeout = new Geary.TimeoutManager.milliseconds(
Util.Gtk.HIDE_PROGRESS_TIMEOUT_MSEC, this.on_hide_progress_timeout
);
this.progress_pulse = new Geary.TimeoutManager.milliseconds(
Util.Gtk.PROGRESS_PULSE_TIMEOUT_MSEC, this.body_progress.pulse
);
this.progress_pulse.repetition = FOREVER;
}
~ConversationMessage() {
@ -562,77 +508,10 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
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.
*/
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.header_revealer, true, include_transitions);
set_revealer(this.body_revealer, true, include_transitions);
@ -816,10 +695,6 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
throw new GLib.IOError.CANCELLED("Conversation load cancelled");
}
if (this.web_view == null) {
initialize_web_view();
}
bool contact_load_images = (
this.primary_contact != null &&
this.primary_contact.load_remote_resources
@ -870,8 +745,6 @@ 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(
search_matches, cancellable
);
@ -885,8 +758,6 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
foreach (ContactFlowBoxChild address in this.searchable_addresses) {
address.unmark_search_terms();
}
if (this.web_view != null)
this.web_view.unmark_search_terms();
}
@ -1050,8 +921,6 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
// returns HTML that is placed into the document in the position
// where the MIME part was found
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;
if (content_type.media_type != "image" ||
!this.web_view.can_show_mime_type(content_type.to_string())) {
@ -1086,7 +955,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
return "<img alt=\"%s\" class=\"%s\" src=\"%s%s\" />".printf(
clean_filename,
REPLACED_IMAGE_CLASS,
Components.WebView.CID_URL_PREFIX,
ClientWebView.CID_URL_PREFIX,
Geary.HTML.escape_markup(id)
);
}
@ -1097,9 +966,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
this.load_remote_resources = true;
this.remote_resources_requested = 0;
this.remote_resources_loaded = 0;
if (this.web_view != null) {
this.web_view.load_remote_images();
}
if (update_email_flag) {
flag_remote_images();
}
@ -1114,12 +981,10 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
if (placeholder != null) {
this.body_placeholder = placeholder;
if (this.web_view != null)
this.web_view.hide();
this.body_container.add(placeholder);
show_message_body(true);
} else {
if (this.web_view != null)
this.web_view.show();
}
}
@ -1148,14 +1013,12 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
}
private void on_is_loading_notify() {
if (this.web_view != null) {
if (this.web_view.is_loading) {
start_progress_loading();
} else {
stop_progress_loading();
}
}
}
private void on_resource_load_started(WebKit.WebView view,
WebKit.WebResource res,
@ -1388,7 +1251,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
alt_text = (string) alt_maybe;
}
if (uri.has_prefix(Components.WebView.CID_URL_PREFIX)) {
if (uri.has_prefix(ClientWebView.CID_URL_PREFIX)) {
// We can get the data directly from the attachment, so
// don't bother getting it from the web view
save_image(uri, alt_text, null);

View file

@ -24,14 +24,6 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
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 Gee.Set<Geary.App.Conversation>? selection_while_composing = null;
@ -254,10 +246,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
Application.ContactStore contacts,
bool start_mark_timer)
throws GLib.Error {
// 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();
remove_current_list();
ConversationListBox new_list = new ConversationListBox(
conversation,
@ -303,9 +292,6 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
}
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
@ -325,7 +311,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
}
// Remove any existing conversation list, cancelling its loading
private Gtk.ScrolledWindow remove_current_list() {
private void remove_current_list() {
if (this.find_cancellable != null) {
this.find_cancellable.cancel();
this.find_cancellable = null;
@ -337,17 +323,15 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
this.current_list = null;
}
var old_scroller = this.conversation_scroller;
// XXX GTK+ Bug 778190 workaround
this.conversation_page.remove(old_scroller);
this.conversation_scroller.destroy(); // removes the list
new_conversation_scroller();
return old_scroller;
}
private void new_conversation_scroller() {
// XXX Work around for GTK+ Bug 778190: Instead of replacing
// the Viewport that contains the current list, replace the
// complete ScrolledWindow. Need to remove this method and
// complete ScrolledWindow. Need to put remove this method and
// put the settings back into conversation-viewer.ui when we
// can rely on it being fixed again.
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.
*/
public class ConversationWebView : Components.WebView {
public class ConversationWebView : ClientWebView {
private const string DECEPTIVE_LINK_CLICKED = "deceptiveLinkClicked";
@ -41,10 +41,10 @@ public class ConversationWebView : Components.WebView {
public static new void load_resources()
throws Error {
ConversationWebView.app_script = Components.WebView.load_app_script(
ConversationWebView.app_script = ClientWebView.load_app_script(
"conversation-web-view.js"
);
ConversationWebView.app_stylesheet = Components.WebView.load_app_stylesheet(
ConversationWebView.app_stylesheet = ClientWebView.load_app_stylesheet(
"conversation-web-view.css"
);
}
@ -56,33 +56,16 @@ public class ConversationWebView : Components.WebView {
);
/**
* 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) {
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_style_sheet(ConversationWebView.app_stylesheet);
}
/**
* Constructs a new web view for displaying an email message body.
*
* The WebKitGTK WebProcess will be shared with the related view's
* process.
*/
internal ConversationWebView.with_related_view(
Application.Configuration config,
ConversationWebView related
) {
base.with_related_view(config, related);
init();
register_message_handler(
DECEPTIVE_LINK_CLICKED, on_deceptive_link_clicked
);
this.notify["preferred-height"].connect(() => queue_resize());
}
/**
@ -212,14 +195,6 @@ public class ConversationWebView : Components.WebView {
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) {
try {
JSC.Value object = result.get_js_value();

View file

@ -28,6 +28,7 @@ geary_client_vala_sources = files(
'client-action.vala',
'components/client-web-view.vala',
'components/components-attachment-pane.vala',
'components/components-entry-undo.vala',
'components/components-inspector.vala',
@ -40,7 +41,6 @@ geary_client_vala_sources = files(
'components/components-reflow-box.c',
'components/components-search-bar.vala',
'components/components-validator.vala',
'components/components-web-view.vala',
'components/count-badge.vala',
'components/folder-popover.vala',
'components/icon-factory.vala',
@ -142,6 +142,16 @@ geary_client_dependencies = [
geary_client_vala_args = geary_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')
geary_client_vala_args += [
'-D', 'HAS_WEBKIT_SHARED_PROC'
]
endif
# Main client application library
geary_client_lib = static_library('geary-client',
geary_client_sources,

View file

@ -32,14 +32,22 @@ public class GearyWebExtension : Object {
private const string[] ALLOWED_SCHEMES = { "cid", "geary", "data", "blob" };
private const string REMOTE_LOAD_VAR = "_gearyAllowRemoteResourceLoads";
private WebKit.WebExtension extension;
public GearyWebExtension(WebKit.WebExtension extension) {
this.extension = extension;
extension.page_created.connect(on_page_created);
extension.page_created.connect((extension, web_page) => {
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
@ -81,7 +89,14 @@ public class GearyWebExtension : Object {
WebKit.Frame frame = page.get_main_frame();
JSC.Context context = frame.get_js_context();
try {
should_load = Util.JS.to_bool(context.get_value(REMOTE_LOAD_VAR));
JSC.Value ret = execute_script(
context,
"geary.allowRemoteImages",
GLib.Log.FILE,
GLib.Log.METHOD,
GLib.Log.LINE
);
should_load = Util.JS.to_bool(ret);
} catch (GLib.Error err) {
debug(
"Error checking PageState::allowRemoteImages: %s",
@ -139,24 +154,4 @@ public class GearyWebExtension : Object {
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,42 +6,35 @@
*/
public abstract class Components.WebViewTestCase<V> : TestCase {
public abstract class ClientWebViewTestCase<V> : TestCase {
protected V? test_view = null;
protected Application.Configuration? config = null;
protected WebViewTestCase(string name) {
protected ClientWebViewTestCase(string name) {
base(name);
}
public override void set_up() {
this.config = new Application.Configuration(Application.Client.SCHEMA_ID);
this.config.enable_debug = true;
WebView.init_web_context(
ClientWebView.init_web_context(
this.config,
File.new_for_path(_BUILD_ROOT_DIR).get_child("src"),
File.new_for_path("/tmp") // XXX use something better here
);
try {
WebView.load_resources(GLib.File.new_for_path("/tmp"));
ClientWebView.load_resources(GLib.File.new_for_path("/tmp"));
} catch (GLib.Error err) {
assert_not_reached();
}
this.test_view = set_up_test_view();
}
protected override void tear_down() {
this.config = null;
this.test_view = null;
public override void set_up() {
this.test_view = set_up_test_view();
}
protected abstract V set_up_test_view();
protected virtual void load_body_fixture(string html = "") {
WebView client_view = (WebView) this.test_view;
ClientWebView client_view = (ClientWebView) this.test_view;
client_view.load_html(html);
while (!client_view.is_content_loaded) {
Gtk.main_iteration();
@ -49,7 +42,7 @@ public abstract class Components.WebViewTestCase<V> : TestCase {
}
protected WebKit.JavascriptResult run_javascript(string command) throws Error {
WebView view = (WebView) this.test_view;
ClientWebView view = (ClientWebView) this.test_view;
view.run_javascript.begin(
command, null, (obj, res) => { async_complete(res); }
);

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
class Composer.PageStateTest : Components.WebViewTestCase<Composer.WebView> {
class Composer.PageStateTest : ClientWebViewTestCase<Composer.WebView> {
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>""";

View file

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

View file

@ -82,16 +82,16 @@ geary_test_client_sources = [
'client/accounts/accounts-manager-test.vala',
'client/application/application-client-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-web-view-test-case.vala',
'client/components/components-web-view-test.vala',
'client/composer/composer-web-view-test.vala',
'client/util/util-avatar-test.vala',
'client/util/util-cache-test.vala',
'client/util/util-email-test.vala',
'client/util/util-js-test.vala',
'js/components-page-state-test.vala',
'js/client-page-state-test.vala',
'js/composer-page-state-test.vala',
'js/conversation-page-state-test.vala',

View file

@ -51,9 +51,9 @@ int main(string[] args) {
client.add_suite(new Accounts.ManagerTest().get_suite());
client.add_suite(new Application.ClientTest().get_suite());
client.add_suite(new Application.ConfigurationTest().get_suite());
client.add_suite(new Components.ValidatorTest().get_suite());
client.add_suite(new Components.WebViewTest().get_suite());
client.add_suite(new ClientWebViewTest().get_suite());
client.add_suite(new Composer.WebViewTest().get_suite());
client.add_suite(new Components.ValidatorTest().get_suite());
client.add_suite(new Util.Avatar.Test().get_suite());
client.add_suite(new Util.Cache.Test().get_suite());
client.add_suite(new Util.Email.Test().get_suite());
@ -61,7 +61,7 @@ int main(string[] args) {
TestSuite js = new TestSuite("js");
js.add_suite(new Components.PageStateTest().get_suite());
js.add_suite(new ClientPageStateTest().get_suite());
js.add_suite(new Composer.PageStateTest().get_suite());
js.add_suite(new ConversationPageStateTest().get_suite());

View file

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

View file

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

View file

@ -9,7 +9,8 @@
<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">certificate_warning_dialog.glade</file>
<file compressed="true">components-web-view.js</file>
<file compressed="true">client-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-menus.ui</file>
<file compressed="true" preprocess="xml-stripblanks">components-attachment-view.ui</file>