1
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
2
|
|
3
|
"""
|
4
|
Making temporary WebExtensions for use in the test suite
|
5
|
"""
|
6
|
|
7
|
# This file is part of Haketilo.
|
8
|
#
|
9
|
# Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
|
10
|
#
|
11
|
# This program is free software: you can redistribute it and/or modify
|
12
|
# it under the terms of the GNU General Public License as published by
|
13
|
# the Free Software Foundation, either version 3 of the License, or
|
14
|
# (at your option) any later version.
|
15
|
#
|
16
|
# This program is distributed in the hope that it will be useful,
|
17
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
18
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
19
|
# GNU General Public License for more details.
|
20
|
#
|
21
|
# You should have received a copy of the GNU General Public License
|
22
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
23
|
#
|
24
|
# I, Wojtek Kosior, thereby promise not to sue for violation of this file's
|
25
|
# license. Although I request that you do not make use of this code in a
|
26
|
# proprietary program, I am not going to enforce this in court.
|
27
|
|
28
|
import json
|
29
|
import zipfile
|
30
|
from pathlib import Path
|
31
|
from uuid import uuid4
|
32
|
from tempfile import TemporaryDirectory
|
33
|
import shutil
|
34
|
import subprocess
|
35
|
|
36
|
from .misc_constants import *
|
37
|
|
38
|
class ManifestTemplateValueToFill:
|
39
|
pass
|
40
|
|
41
|
def manifest_template():
|
42
|
return {
|
43
|
'manifest_version': 2,
|
44
|
'name': 'Haketilo test extension',
|
45
|
'version': '1.0',
|
46
|
'applications': {
|
47
|
'gecko': {
|
48
|
'id': ManifestTemplateValueToFill(),
|
49
|
'strict_min_version': '60.0'
|
50
|
}
|
51
|
},
|
52
|
'permissions': [
|
53
|
'contextMenus',
|
54
|
'webRequest',
|
55
|
'webRequestBlocking',
|
56
|
'activeTab',
|
57
|
'notifications',
|
58
|
'sessions',
|
59
|
'storage',
|
60
|
'tabs',
|
61
|
'<all_urls>',
|
62
|
'unlimitedStorage'
|
63
|
],
|
64
|
'content_security_policy': "object-src 'none'; script-src 'self' https://serve.scrip.ts;",
|
65
|
'web_accessible_resources': ['testpage.html'],
|
66
|
'options_ui': {
|
67
|
'page': 'testpage.html',
|
68
|
'open_in_tab': True
|
69
|
},
|
70
|
'background': {
|
71
|
'persistent': True,
|
72
|
'scripts': ['__open_test_page.js', 'background.js']
|
73
|
},
|
74
|
'content_scripts': [
|
75
|
{
|
76
|
'run_at': 'document_start',
|
77
|
'matches': ['<all_urls>'],
|
78
|
'match_about_blank': True,
|
79
|
'all_frames': True,
|
80
|
'js': ['content.js']
|
81
|
}
|
82
|
]
|
83
|
}
|
84
|
|
85
|
class ExtraHTML:
|
86
|
def __init__(self, html_path, append={}, wrap_into_htmldoc=True):
|
87
|
self.html_path = html_path
|
88
|
self.append = append
|
89
|
self.wrap_into_htmldoc = wrap_into_htmldoc
|
90
|
|
91
|
def add_to_xpi(self, xpi, tmpdir=None):
|
92
|
if tmpdir is None:
|
93
|
with TemporaryDirectory() as tmpdir:
|
94
|
return self.add_to_xpi(xpi, tmpdir)
|
95
|
|
96
|
append_flags = []
|
97
|
for filename, code in self.append.items():
|
98
|
append_flags.extend(['-A', f'{filename}:{code}'])
|
99
|
|
100
|
awk = subprocess.run(
|
101
|
['awk', '-f', awk_script_name, '--', *unit_test_defines,
|
102
|
*append_flags, '-H', self.html_path, '--write-js-deps',
|
103
|
'--output=files-to-copy', f'--output-dir={tmpdir}'],
|
104
|
stdout=subprocess.PIPE, cwd=script_root, check=True
|
105
|
)
|
106
|
|
107
|
for path in filter(None, awk.stdout.decode().split('\n')):
|
108
|
xpi.write(script_root / path, path)
|
109
|
|
110
|
tmpdir = Path(tmpdir)
|
111
|
for path in tmpdir.rglob('*'):
|
112
|
relpath = str(path.relative_to(tmpdir))
|
113
|
if not path.is_dir() and relpath != self.html_path:
|
114
|
xpi.write(path, relpath)
|
115
|
|
116
|
with open(tmpdir / self.html_path, 'rt') as html_file:
|
117
|
html = html_file.read()
|
118
|
if self.wrap_into_htmldoc:
|
119
|
html = f'<!DOCTYPE html><html><body>{html}</body></html>'
|
120
|
xpi.writestr(self.html_path, html)
|
121
|
|
122
|
default_background_script = ''
|
123
|
default_content_script = ''
|
124
|
default_test_page = '''
|
125
|
<!DOCTYPE html>
|
126
|
<html>
|
127
|
<head>
|
128
|
<title>Extension's options page for testing</title>
|
129
|
</head>
|
130
|
<body>
|
131
|
<h1>Extension's options page for testing</h1>
|
132
|
</body>
|
133
|
</html>
|
134
|
'''
|
135
|
|
136
|
open_test_page_script = '''(() => {
|
137
|
const page_url = browser.runtime.getURL("testpage.html");
|
138
|
const execute_details = {
|
139
|
code: `window.wrappedJSObject.ext_page_url=${JSON.stringify(page_url)};`
|
140
|
};
|
141
|
browser.tabs.query({currentWindow: true, active: true})
|
142
|
.then(t => browser.tabs.executeScript(t.id, execute_details));
|
143
|
})();'''
|
144
|
|
145
|
def make_extension(destination_dir,
|
146
|
background_script=default_background_script,
|
147
|
content_script=default_content_script,
|
148
|
test_page=default_test_page,
|
149
|
extra_files={}, extra_html=[]):
|
150
|
if not hasattr(extra_html, '__iter__'):
|
151
|
extra_html = [extra_html]
|
152
|
manifest = manifest_template()
|
153
|
extension_id = '{%s}' % uuid4()
|
154
|
manifest['applications']['gecko']['id'] = extension_id
|
155
|
files = {
|
156
|
'manifest.json' : json.dumps(manifest),
|
157
|
'__open_test_page.js': open_test_page_script,
|
158
|
'background.js' : background_script,
|
159
|
'content.js' : content_script,
|
160
|
'testpage.html' : test_page,
|
161
|
**extra_files
|
162
|
}
|
163
|
destination_path = destination_dir / f'{extension_id}.xpi'
|
164
|
with zipfile.ZipFile(destination_path, 'x') as xpi:
|
165
|
for filename, contents in files.items():
|
166
|
if hasattr(contents, '__call__'):
|
167
|
contents = contents()
|
168
|
xpi.writestr(filename, contents)
|
169
|
for html in extra_html:
|
170
|
html.add_to_xpi(xpi)
|
171
|
|
172
|
return destination_path
|