1# Copyright (c) 2017-2021 Cedric Bellegarde <cedric.bellegarde@adishatz.org>
2# This program is free software: you can redistribute it and/or modify
3# it under the terms of the GNU General Public License as published by
4# the Free Software Foundation, either version 3 of the License, or
5# (at your option) any later version.
6# This program is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9# GNU General Public License for more details.
10# You should have received a copy of the GNU General Public License
11# along with this program. If not, see <http://www.gnu.org/licenses/>.
12
13from gi.repository import Gtk, GObject
14
15from gettext import gettext as _
16from urllib.parse import urlparse
17
18from eolie.logger import Logger
19from eolie.define import App
20from eolie.utils import emit_signal
21from eolie.helper_gestures import GesturesHelper
22
23
24class ScriptRow(Gtk.ListBoxRow, GesturesHelper):
25    """
26        Script row (Allowing to select a script for uri)
27    """
28
29    __gsignals__ = {
30        "activated": (GObject.SignalFlags.RUN_FIRST, None, ()),
31    }
32
33    def __init__(self, uri, toggled):
34        """
35            Init row
36            @param uri as str
37            @param toggled as bool
38        """
39        Gtk.ListBoxRow.__init__(self)
40        self.__uri = uri
41        grid = Gtk.Grid()
42        grid.set_column_spacing(5)
43        grid.show()
44        label = Gtk.Label.new(uri)
45        label.set_hexpand(True)
46        label.set_property("halign", Gtk.Align.START)
47        label.set_tooltip_text(uri)
48        label.show()
49        self.__check = Gtk.CheckButton()
50        self.__check.set_active(toggled)
51        self.__check.connect("toggled", self.__on_check_toggled)
52        self.__check.show()
53        grid.add(self.__check)
54        grid.add(label)
55        eventbox = Gtk.EventBox.new()
56        eventbox.show()
57        eventbox.add(grid)
58        self.add(eventbox)
59        GesturesHelper.__init__(self, eventbox)
60
61    def set_active(self, active):
62        """
63            Set row active
64            @param active as bool
65        """
66        self.__check.set_active(active)
67
68    @property
69    def uri(self):
70        """
71            Get uri
72            @return str
73        """
74        return self.__uri
75
76    @property
77    def is_active(self):
78        """
79            Get Check button
80            @return bool
81        """
82        return self.__check.get_active()
83
84#######################
85# PROTECTED           #
86#######################
87    def _on_primary_press_gesture(self, x, y, event):
88        """
89            Toggle check button
90            @param x as int
91            @param y as int
92            @param event as Gdk.Event
93        """
94        toggled = not self.__check.get_active()
95        self.__check.set_active(toggled)
96
97#######################
98# PRIVATE             #
99#######################
100    def __on_check_toggled(self, button):
101        """
102            Emit activated signal
103            @param button as Gtk.CheckButton
104        """
105        emit_signal(self, "activated")
106
107
108class ScriptsMenu(Gtk.Grid):
109    """
110        Menu allowing user to enable scripts for URI
111    """
112
113    __ALLOW_ALL = _("Allow all scripts")
114
115    def __init__(self, uri, window):
116        """
117            Init widget
118            @param uri as str
119            @param window as Window
120        """
121        Gtk.Grid.__init__(self)
122        self.set_column_spacing(10)
123        self.set_orientation(Gtk.Orientation.VERTICAL)
124        self.__uri = uri
125        self.__window = window
126        self.__back_button = Gtk.ModelButton.new()
127        self.__back_button.show()
128        self.__back_button.set_property("menu_name", "main")
129        self.__back_button.set_property("inverted", True)
130        self.__back_button.set_property("centered", True)
131        self.__back_button.set_property("text", _("Scripts"))
132        self.__scrolled = Gtk.ScrolledWindow.new()
133        self.__scrolled.show()
134        self.__scrolled.set_size_request(-1, 300)
135        self.__scrolled.set_shadow_type(Gtk.ShadowType.NONE)
136        self.__listbox = Gtk.ListBox.new()
137        self.__listbox.show()
138        self.__listbox.get_style_context().add_class("menu-listbox")
139        self.__listbox.set_selection_mode(Gtk.SelectionMode.NONE)
140        self.__listbox.connect("map", self.__on_map)
141        self.set_property("margin", 5)
142        self.__scrolled.add(self.__listbox)
143        self.add(self.__back_button)
144        self.add(self.__scrolled)
145
146#######################
147# PRIVATE             #
148#######################
149    def __is_script_active(self, script, netloc, internal):
150        """
151            True if script active for netloc
152            @param script as str
153            @param netloc as str
154            @param internal as bool
155            @return bool
156        """
157        content_blocker = App().get_content_blocker("block-scripts")
158        if script == ".*":
159            uri = ".*"
160        else:
161            script_parsed = urlparse(script)
162            uri = "%s%s.*" % (script_parsed.netloc, script_parsed.path)
163        result = content_blocker.exceptions.is_domain_exception(
164            netloc, uri, internal)
165        return result
166
167    def __on_map(self, listbox):
168        """
169            Populate languages
170            @param listbox as Gtk.ListBox
171        """
172        if listbox.get_children():
173            return
174        webview = self.__window.container.webview
175        webview.run_javascript_from_gresource(
176                "/org/gnome/Eolie/javascript/GetScripts.js", None,
177                self.__on_get_scripts)
178
179    def __on_get_scripts(self, source, result):
180        """
181            Add scripts to menu
182            @param source as GObject.Object
183            @param result as Gio.AsyncResult
184        """
185        scripts = []
186        try:
187            data = source.run_javascript_from_gresource_finish(result)
188            scripts = data.get_js_value().to_string().split("@@@")
189        except Exception as e:
190            Logger.warning("ScriptsMenu::__on_get_scripts(): %s", e)
191        parsed = urlparse(self.__window.container.webview.uri)
192        allow_all_active = self.__is_script_active('.*', parsed.netloc, False)
193        row = ScriptRow(self.__ALLOW_ALL, allow_all_active)
194        row.connect("activated", self.__on_row_activated, False)
195        row.show()
196        self.__listbox.add(row)
197        for script in scripts:
198            if not script:
199                continue
200            internal = script.find(parsed.netloc) != -1
201            row = ScriptRow(script,
202                            self.__is_script_active(script,
203                                                    parsed.netloc,
204                                                    internal))
205            row.connect("activated", self.__on_row_activated, internal)
206            if allow_all_active:
207                row.set_sensitive(False)
208            row.show()
209            self.__listbox.add(row)
210
211    def __on_row_activated(self, row, internal):
212        """
213            Update exceptions
214            @param row as ScriptRow
215            @param internal as bool
216        """
217        parsed = urlparse(self.__window.container.webview.uri)
218        content_blocker = App().get_content_blocker("block-scripts")
219        if row.uri == self.__ALLOW_ALL:
220            uri = ".*"
221            for _row in self.__listbox.get_children()[1:]:
222                _row.set_active(False)
223                _row.set_sensitive(not row.is_active)
224        else:
225            script_parsed = urlparse(row.uri)
226            uri = "%s%s.*" % (script_parsed.netloc, script_parsed.path)
227        if row.is_active:
228            content_blocker.exceptions.add_domain_exception(
229                parsed.netloc, uri, internal)
230        else:
231            content_blocker.exceptions.remove_domain_exception(
232                parsed.netloc, uri, internal)
233        content_blocker.exceptions.save()
234        content_blocker.update()
235        self.__window.container.webview.reload()
236