Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor web extension class for re-use #379

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions core/plugins.vala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@
*/

namespace Midori {
public class Extension : Object {
internal static List<Extension> extensions = null;

public string id { get; set; }
public string name { get; set; }
public string description { get; set; }
public Icon icon { get; set; default = new ThemedIcon.with_default_fallbacks ("libpeas-plugin-symbolic"); }
public bool available { get; set; default = false; }
public bool active { get {
return Midori.CoreSettings.get_default ().get_plugin_enabled (id);
} set {
Midori.CoreSettings.get_default ().set_plugin_enabled (id, value);
} }

construct {
extensions.append (this);
}

internal Extension (string id, string name, string description, Icon icon) {
Object (id: id, name: name, description: description, icon: icon);
}
}

public class Plugins : Peas.Engine, Loggable {
public string builtin_path { get; construct set; }

Expand Down Expand Up @@ -39,6 +62,24 @@ namespace Midori {
var settings = CoreSettings.get_default ();
foreach (var plugin in get_plugin_list ()) {
debug ("Found plugin %s", plugin.get_name ());
if (!plugin.is_builtin ()) {
var extension = new Extension (
"lib%s.so".printf (plugin.get_module_name ()), plugin.get_name (),
plugin.get_description (), new ThemedIcon.with_default_fallbacks (plugin.get_icon_name ()));
try {
extension.available = plugin.is_available ();
extension.notify["active"].connect (() => {
if (extension.active) {
try_load_plugin (plugin);
}
else {
try_unload_plugin (plugin);
}
});
} catch (Error error) {
critical ("Failed to prepare plugin %s", plugin.get_module_name ());
}
}
if (plugin.is_builtin ()
|| settings.get_plugin_enabled ("lib%s.so".printf (plugin.get_module_name ()))) {
if (!try_load_plugin (plugin)) {
Expand Down
36 changes: 35 additions & 1 deletion core/preferences.vala
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,41 @@ namespace Midori {
add (_("Privacy"), box);

box = new Gtk.Box (Gtk.Orientation.VERTICAL, 4);
box.add (new PeasGtk.PluginManagerView (null));
var plugins = new Gtk.ListBox ();
plugins.selection_mode = Gtk.SelectionMode.NONE;
foreach (var extension in Extension.extensions) {
var row = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 4);
extension.bind_property ("available", row, "sensitive", BindingFlags.SYNC_CREATE);
checkbox = new Gtk.CheckButton ();
extension.bind_property ("active", checkbox, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
row.pack_start (checkbox, false, false, 4);
var icon = new Gtk.Image.from_gicon (extension.icon, Gtk.IconSize.BUTTON);
// Icon size only applies to named icons
if (extension.icon is Gdk.Pixbuf) {
int icon_width = 16, icon_height = 16;
Gtk.icon_size_lookup ((Gtk.IconSize)icon.icon_size, out icon_width, out icon_height);
// Take scale factor into account
icon_width *= scale_factor;
icon_height *= scale_factor;
icon.pixbuf = ((Gdk.Pixbuf)extension.icon).scale_simple (icon_width, icon_height, Gdk.InterpType.BILINEAR);
}
row.pack_start (icon, false, false, 4);
var label = new Gtk.Box (Gtk.Orientation.VERTICAL, 4);
var name = new Gtk.Label (extension.name);
name.ellipsize = Pango.EllipsizeMode.END;
name.xalign = 0.0f;
label.pack_start (name);
var description = new Gtk.Label (extension.description);
description.ellipsize = Pango.EllipsizeMode.END;
description.xalign = 0.0f;
label.pack_start (description);
row.pack_end (label, true, true, 4);
plugins.add (row);
}
var scrolled = new Gtk.ScrolledWindow (null, null);
scrolled.min_content_height = 333;
scrolled.add (plugins);
box.add (scrolled);
box.show_all ();
add (_("Extensions"), box);

Expand Down
134 changes: 85 additions & 49 deletions extensions/web-extensions.vala
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
*/

namespace WebExtension {
public class Extension : Object {
public class Extension : Midori.Extension {
HashTable<string, Bytes> _files;
public File file { get; protected set; }

public string name { get; set; }
public string description { get; set; }
public string? background_page { get; owned set; }
public List<string> background_scripts { get; owned set; }
public List<string> content_scripts { get; owned set; }
Expand All @@ -24,7 +22,7 @@ namespace WebExtension {
public Action? sidebar { get; set; }

public Extension (File file) {
Object (file: file, name: file.get_basename ());
Object (id: file.get_basename (), file: file, name: file.get_basename ());
}

public void add_resource (string resource, Bytes data) {
Expand Down Expand Up @@ -79,6 +77,8 @@ namespace WebExtension {
// "WebExtensionExtensionManager::extension_added" is not a value type
public signal void extension_added (Object extension);

public signal void extension_removed (Object extension);

string? pick_default_icon (Json.Object action) {
if (action.has_member ("default_icon")) {
var node = action.get_member ("default_icon");
Expand All @@ -103,21 +103,17 @@ namespace WebExtension {
return _default;
}

public async void load_from_folder (WebKit.UserContentManager content, File folder) throws Error {
public async void load_from_folder (File folder) throws Error {
debug ("Load web extensions from %s", folder.get_path ());
var enumerator = yield folder.enumerate_children_async (FileAttribute.STANDARD_NAME, 0);
FileInfo info;
while ((info = enumerator.next_file ()) != null) {
var file = folder.get_child (info.get_name ());
string id = file.get_basename ();
if (!Midori.CoreSettings.get_default ().get_plugin_enabled (id)) {
continue;
}

var extension = extensions.lookup (id);
if (extension == null) {
InputStream? stream = null;
extension = new Extension (file);

try {
// Try reading from a ZIP archive ie. .crx (Chrome/ Opera/ Vivaldi), .nex (Opera) or .xpi (Firefox)
Expand All @@ -126,6 +122,7 @@ namespace WebExtension {
var archive = new Archive.Read ();
archive.support_format_zip ();
if (archive.open_filename (file.get_path (), 10240) == Archive.Result.OK) {
extension = new Extension (file);
unowned Archive.Entry entry;
while (archive.next_header (out entry) == Archive.Result.OK) {
if (entry.pathname () == "manifest.json") {
Expand Down Expand Up @@ -153,6 +150,7 @@ namespace WebExtension {
// If we find a manifest, this is a web extension
var manifest_file = file.get_child ("manifest.json");
if (manifest_file.query_exists ()) {
extension = new Extension (file);
stream = new DataInputStream (yield manifest_file.read_async ());
} else {
continue;
Expand All @@ -167,6 +165,9 @@ namespace WebExtension {
if (manifest.has_member ("name")) {
extension.name = manifest.get_string_member ("name");
}
if (manifest.has_member ("description")) {
extension.description = manifest.get_string_member ("description");
}

if (manifest.has_member ("background")) {
var background = manifest.get_object_member ("background");
Expand All @@ -193,8 +194,21 @@ namespace WebExtension {
}
}

if (manifest.has_member ("icons")) {
var node = manifest.get_member ("icons");
if (node.get_node_type () == Json.NodeType.OBJECT) {
foreach (var size in node.get_object ().get_members ()) {
var image = yield extension.get_resource (node.get_object ().get_string_member (size));
// Note: The from_bytes variant has no autodetection
var image_stream = new MemoryInputStream.from_data (image.get_data (), free);
extension.icon = yield new Gdk.Pixbuf.from_stream_async (image_stream, null);
break;
}
}
}

if (manifest.has_member ("sidebar_action")) {
var sidebar = manifest.has_member ("sidebar_action") ? manifest.get_object_member ("sidebar_action") : null;
var sidebar = manifest.get_object_member ("sidebar_action");
if (sidebar != null) {
extension.sidebar = new Action (
pick_default_icon (sidebar),
Expand All @@ -220,33 +234,20 @@ namespace WebExtension {
}
}

extension.available = true;
extension.notify["active"].connect (() => {
if (extension.active) {
extension_added (extension);
} else {
extension_removed (extension);
}
});
extensions.insert (id, extension);
extension_added (extension);
} catch (Error error) {
warning ("Failed to load extension '%s': %s\n", extension.name, error.message);
}
}

foreach (var filename in extension.content_scripts) {
try {
var script = yield extension.get_resource (filename);
content.add_script (new WebKit.UserScript ((string)(script.get_data ()),
WebKit.UserContentInjectedFrames.TOP_FRAME,
WebKit.UserScriptInjectionTime.END,
null, null));
} catch (Error error) {
warning ("Failed to inject content script for '%s': %s", extension.name, filename);
}
}
foreach (var filename in extension.content_styles) {
try {
var stylesheet = yield extension.get_resource (filename);
content.add_style_sheet (new WebKit.UserStyleSheet ((string)(stylesheet.get_data ()),
WebKit.UserContentInjectedFrames.TOP_FRAME,
WebKit.UserStyleLevel.USER,
null, null));
if (extension.active) {
extension_added (extension);
}
} catch (Error error) {
warning ("Failed to inject content stylesheet for '%s': %s", extension.name, filename);
warning ("Failed to load extension '%s': %s\n", extension != null ? extension.name : id, error.message);
}
}
}
Expand Down Expand Up @@ -363,7 +364,7 @@ namespace WebExtension {
tooltip_text = extension.browser_action.title ?? extension.name;
visible = true;
focus_on_click = false;
var icon = new Gtk.Image.from_icon_name ("midori-symbolic", Gtk.IconSize.BUTTON);
var icon = new Gtk.Image.from_icon_name ("libpeas-plugin-symbolic", Gtk.IconSize.BUTTON);
icon.use_fallback = true;
icon.visible = true;
if (extension.browser_action.icon != null) {
Expand Down Expand Up @@ -430,6 +431,31 @@ namespace WebExtension {
}

async void install_extension (Extension extension) throws Error {
var content = browser.tab.get_user_content_manager ();
foreach (var filename in extension.content_scripts) {
try {
var script = yield extension.get_resource (filename);
content.add_script (new WebKit.UserScript ((string)(script.get_data ()),
WebKit.UserContentInjectedFrames.TOP_FRAME,
WebKit.UserScriptInjectionTime.END,
null, null));
} catch (Error error) {
warning ("Failed to inject content script for '%s': %s", extension.name, filename);
}
}

foreach (var filename in extension.content_styles) {
try {
var stylesheet = yield extension.get_resource (filename);
content.add_style_sheet (new WebKit.UserStyleSheet ((string)(stylesheet.get_data ()),
WebKit.UserContentInjectedFrames.TOP_FRAME,
WebKit.UserStyleLevel.USER,
null, null));
} catch (Error error) {
warning ("Failed to inject content stylesheet for '%s': %s", extension.name, filename);
}
}

if (extension.browser_action != null) {
browser.add_button (new Button (extension as Extension));
}
Expand Down Expand Up @@ -476,14 +502,6 @@ namespace WebExtension {
extension_scheme.begin (request);
});

var manager = ExtensionManager.get_default ();
manager.extension_added.connect ((extension) => {
install_extension.begin ((Extension)extension);
});
manager.foreach ((extension) => {
install_extension.begin ((Extension)extension);
});

browser.tabs.add.connect (tab_added);
if (browser.tab != null) {
tab_added (browser.tab);
Expand All @@ -494,18 +512,34 @@ namespace WebExtension {
browser.tabs.add.disconnect (tab_added);

var manager = ExtensionManager.get_default ();
var tab = widget as Midori.Tab;
manager.extension_added.connect ((extension) => {
install_extension.begin ((Extension)extension);
});
manager.extension_removed.connect ((extension) => {
((Extension)extension).available = false;
});
manager.foreach ((extension) => {
if (extension.active) {
install_extension.begin ((Extension)extension);
}
});
}
}

public class App : Peas.ExtensionBase, Midori.AppActivatable {
public Midori.App app { owned get; set; }

var content = tab.get_user_content_manager ();
public void activate () {
var manager = ExtensionManager.get_default ();
// Try and load plugins from build folder
var builtin_path = ((Midori.App)Application.get_default ()).exec_path.get_parent ().get_child ("extensions");
manager.load_from_folder.begin (content, builtin_path);
manager.load_from_folder.begin (builtin_path);
// System-wide plugins
manager.load_from_folder.begin (content, File.new_for_path (Config.PLUGINDIR));
manager.load_from_folder.begin (File.new_for_path (Config.PLUGINDIR));
// Plugins installed by the user
string user_path = Path.build_path (Path.DIR_SEPARATOR_S,
Environment.get_user_data_dir (), Config.PROJECT_NAME, "extensions");
manager.load_from_folder.begin (content, File.new_for_path (user_path));
manager.load_from_folder.begin (File.new_for_path (user_path));
}
}
}
Expand All @@ -514,4 +548,6 @@ namespace WebExtension {
public void peas_register_types(TypeModule module) {
((Peas.ObjectModule)module).register_extension_type (
typeof (Midori.BrowserActivatable), typeof (WebExtension.Browser));
((Peas.ObjectModule)module).register_extension_type (
typeof (Midori.AppActivatable), typeof (WebExtension.App));
}