14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tests/test_convert/backgroundScripts_mv2/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "background.scripts.mv2",
3 | "description": "Background scripts mv2",
4 | "author": "Solomon Kinard",
5 | "icons": {"128": "icon.png"},
6 | "incognito": "split",
7 | "manifest_version": 2,
8 | "version": "1.0",
9 | "browser_action": {"default_popup": "popup.html"},
10 | "permissions": ["storage"],
11 | "background": {
12 | "scripts": [
13 | "script1.js",
14 | "script2.js"
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/test_convert/test23contentSecurityPolicy/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test2contentSecurityPolicy",
3 | "description": "",
4 | "author": "Solomon Kinard",
5 | "incognito": "split",
6 | "manifest_version": 2,
7 | "version": "1.0",
8 | "content_security_policy": "script-src 'self' https://cdn.firebase.com https://*.firebaseio.com; object-src 'self'",
9 | "sandbox": {"content_security_policy": "script-src 'self' https://cdn.firebase.com https://*.firebaseio.com; object-src 'self'"}
10 | }
11 |
--------------------------------------------------------------------------------
/tests/test_convert/todolist_mv3/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Todos",
3 | "description": "TODO list made simple.",
4 | "author": "Solomon Kinard",
5 | "icons": {"128": "icon.png"},
6 | "incognito": "split",
7 | "manifest_version": 3,
8 | "version": "1.0",
9 | "action": {"default_popup": "popup.html"},
10 | "permissions": ["storage"],
11 | "options_ui": {
12 | "page": "options.html",
13 | "open_in_tab": false
14 | },
15 | "background": {
16 | "service_worker": "service_worker.js"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/test_convert/todolist_mv3/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | My Test Extension Options
4 |
5 |
6 | Font size:
7 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/tests/test_convert/test32webAccessibleResources/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test23webAccessibleResources",
3 | "description": "",
4 | "author": "Solomon Kinard",
5 | "incognito": "split",
6 | "manifest_version": 3,
7 | "version": "1.0",
8 | "web_accessible_resources": [
9 | {
10 | "resources": [
11 | "1.html",
12 | "2.html"
13 | ],
14 | "matches": [
15 | ""
16 | ]
17 | }
18 | ],
19 | "permissions": [
20 | "activeTab",
21 | "scripting"
22 | ],
23 | "background": {
24 | "service_worker": "service_worker.js"
25 | },
26 | "action": {}
27 | }
--------------------------------------------------------------------------------
/tests/test_convert/todolist_mv3/popup.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: black;
3 | color: white;
4 | min-width: 400px;
5 | }
6 |
7 | .nobr {
8 | white-space: nowrap
9 | }
10 |
11 | .margin-right-half {
12 | margin-right: 0.5em;
13 | }
14 |
15 | #addText {
16 | width: 100%;
17 | background-color: black;
18 | color: white;
19 | }
20 |
21 | .button {
22 | border-radius: 50%;
23 | transition-duration: 0.4s;
24 | color: white;
25 | background-color: Transparent;
26 | background-repeat:no-repeat;
27 | border: none;
28 | cursor:pointer;
29 | overflow: hidden;
30 | outline:none;
31 | }
32 |
33 | .button:hover {
34 | color: orange;
35 | }
36 |
--------------------------------------------------------------------------------
/tests/test_convert/backgroundScripts_mv2/popup.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: black;
3 | color: white;
4 | min-width: 400px;
5 | }
6 |
7 | .nobr {
8 | white-space: nowrap
9 | }
10 |
11 | .margin-right-half {
12 | margin-right: 0.5em;
13 | }
14 |
15 | #addText {
16 | width: 100%;
17 | background-color: black;
18 | color: white;
19 | }
20 |
21 | .button {
22 | border-radius: 50%;
23 | transition-duration: 0.4s;
24 | color: white;
25 | background-color: Transparent;
26 | background-repeat:no-repeat;
27 | border: none;
28 | cursor:pointer;
29 | overflow: hidden;
30 | outline:none;
31 | }
32 |
33 | .button:hover {
34 | color: orange;
35 | }
36 |
--------------------------------------------------------------------------------
/src/modifiers/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from ..logger import Logger
13 | from .Modifier import Modifier
14 |
--------------------------------------------------------------------------------
/tests/test_convert/test23executeScript/script.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | console.log('1 from script.');
15 |
--------------------------------------------------------------------------------
/tests/test_convert/backgroundScripts_mv2/script1.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | function script1() {
15 | alert('script1');
16 | }
17 |
--------------------------------------------------------------------------------
/tests/test_convert/backgroundScripts_mv2/script2.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | function script2() {
15 | alert('script2');
16 | }
17 |
--------------------------------------------------------------------------------
/emc.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from src.frontend import Frontend
13 | from utils.arguments import Arguments
14 |
15 | def main():
16 | args = Arguments()
17 | args.parse()
18 | if args.error:
19 | print(args.error)
20 | return
21 | Frontend(args.dict)
22 |
23 |
24 | if __name__ == '__main__':
25 | main()
26 |
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | import unittest
13 | import os
14 |
15 | from tests.test_type import TestType
16 | from tests.test_convert import TestConvert
17 |
18 | class Test(unittest.TestCase):
19 | def setUp(self):
20 | # TestExtension()
21 | TestType()
22 | TestConvert()
23 |
24 |
25 | if __name__ == '__main__':
26 | unittest.main()
27 |
--------------------------------------------------------------------------------
/tests/test_convert/test23webAccessibleResources/background.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | chrome.browserAction.onClicked.addListener(tab => {
15 | let url1 = chrome.runtime.getURL("1.html");
16 | let url2 = chrome.runtime.getURL('2.html');
17 | chrome.tabs.executeScript(
18 | null,
19 | {
20 | code: `document.body.innerHTML += ''`
21 | }
22 | );
23 | });
24 |
--------------------------------------------------------------------------------
/utils/zip.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from zipfile import ZipFile
13 | import os
14 | from os.path import basename
15 | import shutil
16 | import sys
17 |
18 |
19 | class Zip:
20 | def zip(self, source, destination):
21 | shutil.make_archive(destination, 'zip', source)
22 |
23 |
24 | def main():
25 | if len(sys.argv) != 2:
26 | return
27 | path = sys.argv[1]
28 | Zip().zip(path, 'zip', '.')
29 |
30 |
31 | if __name__ == '__main__':
32 | main()
33 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
--------------------------------------------------------------------------------
/src/modifiers/ManifestVersionModifier.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from . import Modifier
13 | from . import Logger
14 |
15 | class ManifestVersionModifier(Modifier):
16 | def _mv2(self):
17 | self.__common()
18 |
19 | def _mv3(self):
20 | self.__common()
21 |
22 | def __common(self):
23 | Logger().log("Changing manifest_version to {}".format(self.wrapper.getManifestVersion()))
24 | self.wrapper.manifest['manifest_version'] = self.wrapper.getManifestVersion()
25 | self.writeManifest()
26 |
--------------------------------------------------------------------------------
/src/convert.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | import sys
13 | import os
14 |
15 | from .logger import Logger
16 | from .worker import Worker
17 |
18 | def main():
19 | if len(sys.argv) != 2:
20 | Logger().log("Missing source directory argument.", 0)
21 | return
22 |
23 | if not os.path.isdir(sys.argv[1]):
24 | Logger().log("Source directory doesn't exist.", 0)
25 | return
26 |
27 | source = sys.argv[1]
28 | worker = Worker()
29 | worker.work(source)
30 |
31 |
32 | if __name__ == '__main__':
33 | main()
34 |
--------------------------------------------------------------------------------
/tests/test_convert/test32webAccessibleResources/service_worker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | chrome.action.onClicked.addListener(tab => {
15 | chrome.scripting.executeScript({
16 | target: { tabId: tab.id },
17 | function: () => {
18 | let url1 = chrome.runtime.getURL("1.html");
19 | let url2 = chrome.runtime.getURL('2.html');
20 | document.body.innerHTML += ``;
21 | document.body.innerHTML += ``;
22 | }
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/logger.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | class Logger:
13 | def __new__(cls):
14 | if cls._instance is None:
15 | cls._instance = super(Logger, cls).__new__(cls)
16 | cls._lines = []
17 | return cls._instance
18 |
19 | _instance = None
20 | _log_level = 2 # 0: error, 1: warn, 2: status, 3: verbose
21 |
22 | def log(self, message, level=2):
23 | if level > self._log_level: return
24 | prefix = ""
25 | if level == 0:
26 | prefix = "Error: "
27 | elif level == 1:
28 | prefix = "Warn: "
29 | print("{}{}".format(prefix, message))
30 |
--------------------------------------------------------------------------------
/src/type.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | import os
13 |
14 | from enum import Enum
15 |
16 | class TypeEnum(Enum):
17 | MANIFEST = 0
18 | ZIP = 1
19 | DIR = 2
20 | UNKNOWN = 3
21 |
22 | class Type():
23 | def getFileType(self, name):
24 | basename = os.path.basename(name)
25 | _, file_extension = os.path.splitext(name)
26 | if basename == "manifest.json":
27 | return TypeEnum.MANIFEST
28 | elif file_extension == '.zip':
29 | return TypeEnum.ZIP
30 | elif os.path.isdir(name):
31 | return TypeEnum.DIR
32 | else:
33 | return TypeEnum.UNKNOWN
34 |
--------------------------------------------------------------------------------
/src/modifiers/WebAccessibleResourcesModifier.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from . import Modifier
13 | from . import Logger
14 |
15 | class WebAccessibleResourcesModifier(Modifier):
16 | def _mv2(self):
17 | pass
18 |
19 | def _mv3(self):
20 | manifest = self.wrapper.manifest
21 | key = 'web_accessible_resources'
22 | if key in manifest:
23 | Logger().log("Updating {}".format(key))
24 | resources = manifest[key]
25 | candidate = {
26 | "resources": resources,
27 | "matches": [""]
28 | }
29 | self.wrapper.manifest[key] = [candidate]
30 | self.writeManifest()
31 |
--------------------------------------------------------------------------------
/tests/test_convert/test23missingPermissions/background.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | chrome.browserAction.onClicked.addListener(tab => {
15 | chrome.tabs.executeScript(
16 | null,
17 | {
18 | code: 'alert("1 from code.");'
19 | }
20 | );
21 | });
22 |
23 | /*
24 | The logic to handle a convenience method would be trickier.
25 | Each of the callsites would need to do something different.
26 | Maybe it would work anyway, but it's unlikly without care. e.g.
27 |
28 | function execute(tab, code) {
29 | chrome.tabs.executeScript(
30 | tab,
31 | {code: code},
32 | );
33 | }
34 | */
35 |
--------------------------------------------------------------------------------
/utils/arguments.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | import os
13 | import sys
14 |
15 | class Arguments:
16 | dict = {}
17 | error = ""
18 |
19 | def parse(self) -> None:
20 | if len(sys.argv) == 1:
21 | self.error = self.usage()
22 | return
23 |
24 | if not os.path.exists(sys.argv[1]):
25 | self.error = "Source directory doesn't exist."
26 | return
27 |
28 | dict = {}
29 | dict['source'] = sys.argv[1]
30 | self.dict = dict
31 |
32 | def usage(self) -> None:
33 | return """usage: python3 emc.py
34 |
35 | A valid path is one of the following:
36 | Extension zip file
37 | Extension manifest
38 | Extension directory
39 |
40 | Test: python3 test.py"""
41 |
--------------------------------------------------------------------------------
/src/modifiers/Modifier.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | import json
13 | import os
14 |
15 | from . import Logger
16 |
17 | class Modifier:
18 | wrapper = None
19 |
20 | def __init__(self, wrapper):
21 | Logger().log("Started " + type(self).__name__, 3)
22 | self.wrapper = wrapper
23 |
24 | def run(self):
25 | self._mv2() if self.wrapper.getManifestVersion() == 2 else self._mv3()
26 |
27 | def writeManifest(self):
28 | manifest_file = self.wrapper.destination + '/manifest.json'
29 | if not os.path.exists(manifest_file):
30 | return
31 | if os.path.exists(manifest_file):
32 | with open(manifest_file, 'w', encoding='UTF-8') as outfile:
33 | json.dump(self.wrapper.manifest, outfile, indent=2)
34 |
--------------------------------------------------------------------------------
/src/modifiers/ManifestActionModifier.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from . import Modifier
13 | from . import Logger
14 |
15 | class ManifestActionModifier(Modifier):
16 | def _mv2(self):
17 | pass # action works fine both ways
18 |
19 | def _mv3(self):
20 | manifest = self.wrapper.manifest
21 | isChanged = False
22 | if 'browser_action' in manifest:
23 | isChanged = True
24 | manifest['action'] = manifest['browser_action']
25 | del manifest['browser_action']
26 | elif 'page_action' in manifest:
27 | isChanged = True
28 | manifest['action'] = manifest['page_action']
29 | del manifest['page_action']
30 | if not isChanged: return
31 | Logger().log("Changed to chrome.action in manifest.json")
32 | self.writeManifest()
33 |
--------------------------------------------------------------------------------
/tests/test_convert/todolist_mv3/service_worker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | var items = [];
15 | var textarea = "";
16 |
17 | chrome.runtime.onConnect.addListener(function(port) {
18 | if (port.name === "popup") {
19 | chrome.storage.local.get([key], function(result) {
20 | if (result[key]) {
21 | textarea = result[key];
22 | port.postMessage({text: textarea});
23 | }
24 | });
25 |
26 | port.onDisconnect.addListener(function() {
27 | save();
28 | });
29 |
30 | port.onMessage.addListener(function(msg) {
31 | textarea = msg.text;
32 | });
33 | }
34 | });
35 |
36 | var key = 'todoListTextarea';
37 |
38 | function save() {
39 | chrome.storage.local.set({[key]: textarea}, () => {});
40 | }
41 |
--------------------------------------------------------------------------------
/src/wrapper.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | class Wrapper:
13 | destination = ""
14 | manifest = {}
15 |
16 | def __init__(self, destination, manifest):
17 | self.destination = destination
18 | self.setManifest(manifest)
19 |
20 | __manifest_version = {
21 | "new": 0,
22 | "old": 0,
23 | }
24 |
25 | def setManifest(self, manifest):
26 | self.manifest = manifest
27 | key = "manifest_version"
28 | if key in manifest:
29 | self.setManifestVersion(manifest["manifest_version"])
30 | else:
31 | self.setManifestVersion(-1)
32 |
33 | def setManifestVersion(self, version):
34 | self.__manifest_version["old"] = version
35 | self.__manifest_version["new"] = 3 if version == 2 else 2
36 |
37 | def getManifestVersion(self):
38 | return self.__manifest_version["new"]
39 |
--------------------------------------------------------------------------------
/src/modifiers/ContentSecurityPolicyModifier.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from . import Modifier
13 | from . import Logger
14 |
15 | class ContentSecurityPolicyModifier(Modifier):
16 | def _mv2(self):
17 | pass
18 |
19 | def _mv3(self):
20 | candidate = {}
21 | manifest = self.wrapper.manifest
22 | key = 'content_security_policy'
23 | if key in manifest:
24 | value = manifest[key]
25 | candidate["extension_pages"] = value
26 | if "sandbox" in manifest and key in manifest["sandbox"]:
27 | candidate["sandbox"] = manifest["sandbox"][key]
28 | del manifest["sandbox"]
29 | if candidate:
30 | Logger().log("Changing CSP (content_security_policy) in manifest.json")
31 | Logger().log("Valid CSP directives for {script,object,worker}-src are {self,none,localhost,127.0.0.1}.")
32 |
33 | self.wrapper.manifest[key] = candidate
34 | self.writeManifest()
35 |
--------------------------------------------------------------------------------
/tests/test_convert/test23executeScript/background.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | chrome.browserAction.onClicked.addListener(tab => {
15 | chrome.tabs.executeScript(
16 | null,
17 | {
18 | file: 'script.js',
19 | runAt: 'document_start'
20 | }
21 | );
22 |
23 | chrome.tabs.executeScript(
24 | null,
25 | {
26 | code: 'console.log("1 from code.");'
27 | }
28 | );
29 |
30 | chrome.tabs.executeScript(
31 | null,
32 | {
33 | code: 'console.log("2 from code.");'
34 | }
35 | );
36 | });
37 |
38 | /*
39 | The logic to handle a convenience method would be trickier.
40 | Each of the callsites would need to do something different.
41 | Maybe it would work anyway, but it's unlikly without care. e.g.
42 |
43 | function execute(tab, code) {
44 | chrome.tabs.executeScript(
45 | tab,
46 | {code: code},
47 | );
48 | }
49 | */
50 |
--------------------------------------------------------------------------------
/src/modifiers/HostPermissionsModifier.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from . import Modifier
13 | from . import Logger
14 |
15 | class HostPermissionsModifier(Modifier):
16 | def _mv2(self):
17 | pass
18 |
19 | def _mv3(self):
20 | manifest = self.wrapper.manifest
21 | keys = ['permissions', 'optional_permissions']
22 | result = {
23 | 'permissions': [],
24 | 'optional_permissions': [],
25 | 'host_permissions': []
26 | }
27 | for key in keys:
28 | if key not in manifest: continue
29 | for item in manifest[key]:
30 | if item.find('//') == -1:
31 | result[key].append(item)
32 | else:
33 | Logger().log("Moving to host_permissions: {}".format(item))
34 | result['host_permissions'].append(item)
35 |
36 | for item in result:
37 | if len(result[item]) > 0:
38 | self.wrapper.manifest[item] = result[item]
39 | self.writeManifest()
40 |
--------------------------------------------------------------------------------
/tests/test_type.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | import unittest
13 | import os
14 |
15 | from src.type import Type, TypeEnum
16 |
17 | class TestType(unittest.TestCase):
18 | cwd = os.path.dirname(os.path.abspath(__file__))
19 | prefix = 'tests' + os.sep + 'test_type' + os.sep
20 | worker = None
21 |
22 | def setUp(self):
23 | self.type = Type()
24 |
25 | def tearDown(self):
26 | pass
27 |
28 | def test_zip(self):
29 | type = self.type.getFileType(self.prefix + "a.zip")
30 | self.assertEqual(type, TypeEnum.ZIP)
31 |
32 | def test_manifest(self):
33 | type = self.type.getFileType(self.prefix + "manifest.json")
34 | self.assertEqual(type, TypeEnum.MANIFEST)
35 |
36 | def test_dir(self):
37 | type = self.type.getFileType(self.prefix + "dir")
38 | self.assertEqual(type, TypeEnum.DIR)
39 |
40 | def test_unknown(self):
41 | type = self.type.getFileType(self.prefix + "unknown")
42 | self.assertEqual(type, TypeEnum.UNKNOWN)
43 |
44 |
45 | if __name__ == '__main__':
46 | unittest.main()
47 |
--------------------------------------------------------------------------------
/tests/test_convert/timebadge_mv3/worker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | chrome.alarms.onAlarm.addListener(function( alarm ) {
15 | setBadgeText();
16 | });
17 |
18 | function setBadgeText() {
19 | let today = new Date();
20 | let h = maybePrependZero(today.getHours());
21 | let m = maybePrependZero(today.getMinutes());
22 | chrome.action.setBadgeText({text: `${h}${m}`});
23 | }
24 |
25 | function maybePrependZero(x) {
26 | return x < 10 ? "0" + x : x;
27 | }
28 |
29 | function clearBadgeText() {
30 | chrome.action.setBadgeText({text: ""});
31 | }
32 |
33 | function toggleAlarm() {
34 | chrome.alarms.getAll(function(alarms) {
35 | if (alarms.length > 0) {
36 | chrome.alarms.clearAll();
37 | clearBadgeText();
38 | } else {
39 | setBadgeText();
40 | createAlarm();
41 | }
42 | });
43 | }
44 |
45 | function createAlarm() {
46 | let date = new Date();
47 | let seconds = date.getSeconds();
48 | chrome.alarms.create("time", {
49 | periodInMinutes: 1,
50 | when: Date.now() + (60-seconds*1000)
51 | });
52 | }
53 |
54 | chrome.action.onClicked.addListener(() => toggleAlarm());
55 |
--------------------------------------------------------------------------------
/tests/test_convert/todolist_mv3/options.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | // Saves options to chrome.storage
15 | function save_options() {
16 | var fontSize = document.getElementById('font-size').value;
17 | chrome.storage.local.set({
18 | todoListFontSize: fontSize
19 | }, function() {
20 | setFontSize(fontSize);
21 | // Update status to let user know options were saved.
22 | var status = document.getElementById('status');
23 | status.textContent = 'Options saved.';
24 | setTimeout(function() {
25 | status.textContent = '';
26 | }, 1000);
27 | });
28 | }
29 |
30 | // Restores select box and checkbox state using the preferences
31 | // stored in chrome.storage.
32 | function restoreFontSize() {
33 | chrome.storage.local.get({
34 | todoListFontSize: '90'
35 | }, function(items) {
36 | setFontSize(items.todoListFontSize);
37 | });
38 | }
39 |
40 | function setFontSize(size) {
41 | document.body.style.fontSize = size + '%';
42 | }
43 |
44 | document.addEventListener('DOMContentLoaded', restoreFontSize());
45 | document.getElementById('save').addEventListener('click',
46 | save_options);
47 |
--------------------------------------------------------------------------------
/src/modifiers/JSActionModifier.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from pathlib import Path
13 | import os
14 |
15 | from . import Modifier
16 | from . import Logger
17 |
18 | class JSActionModifier(Modifier):
19 | def _mv2(self):
20 | pass # action works fine both ways
21 |
22 | def _mv3(self):
23 | for root, dirs, files in os.walk(self.wrapper.destination, topdown=False):
24 | for name in files:
25 | if str(Path(name).suffix) != '.js': continue
26 | path = root + os.sep + os.sep.join(dirs) + name
27 | if os.path.exists(path):
28 | with open(path, 'r+', encoding='UTF-8') as file:
29 | search = ['chrome.browserAction.', 'chrome.pageAction.']
30 | replace = 'chrome.action.'
31 | data = file.read()
32 | isChanged = False
33 | for item in search:
34 | if data.find(item) == -1: continue
35 | if not isChanged: isChanged = True
36 | data = data.replace(item, replace)
37 | if not isChanged: return
38 | Logger().log("Changed to chrome.action in .js files")
39 | file.seek(0)
40 | file.write(data)
41 | file.truncate()
42 |
--------------------------------------------------------------------------------
/src/modifiers/ServiceWorkerModifier.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | import os
13 |
14 | from . import Modifier
15 | from . import Logger
16 |
17 | class ServiceWorkerModifier(Modifier):
18 | def _mv2(self):
19 | manifest = self.wrapper.manifest
20 | if 'background' in manifest and 'service_worker' in manifest['background']:
21 | manifest['background']['scripts'] = [manifest['background']['service_worker']]
22 | del manifest['background']['service_worker']
23 | self.wrapper.manifest = manifest
24 | self.writeManifest()
25 |
26 | def _mv3(self):
27 | manifest = self.wrapper.manifest
28 | if 'background' not in manifest or 'scripts' not in manifest['background']:
29 | return
30 | Logger().log("Changing to background.service_worker in manifest.json")
31 | new_filename = 'service_worker.js'
32 | path = self.wrapper.destination + os.sep + new_filename
33 | filenames = manifest['background']['scripts']
34 | with open(path, 'w', encoding='UTF-8') as outfile:
35 | for fname in filenames:
36 | scriptFile = self.wrapper.destination + os.sep + fname
37 | if os.path.exists(scriptFile):
38 | with open(scriptFile, encoding='UTF-8') as infile:
39 | for line in infile:
40 | outfile.write(line)
41 | os.remove(scriptFile)
42 | manifest['background']['service_worker'] = new_filename
43 | del manifest['background']['scripts']
44 | self.wrapper.manifest = manifest
45 | self.writeManifest()
46 |
--------------------------------------------------------------------------------
/src/modifiers/InsertCssModifier.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from pathlib import Path
13 | import os
14 |
15 | from . import Modifier
16 | from . import Logger
17 |
18 | class InsertCssModifier(Modifier):
19 | def _mv2(self):
20 | pass
21 |
22 | def _mv3(self):
23 | needsChange = False
24 | for root, dirs, files in os.walk(self.wrapper.destination, topdown=False):
25 | for name in files:
26 | if str(Path(name).suffix) != '.js': continue
27 | path = root + os.sep + os.sep.join(dirs) + name
28 | if os.path.exists(path):
29 | with open(path, 'r+', encoding='UTF-8') as file:
30 | data = file.read()
31 | seek = 'chrome.tabs.insertCSS'
32 | if data.find(seek) == -1: continue
33 | log("Changing to chrome.scripting.insertCSS")
34 | if not needsChange: needsChange = True
35 | file.seek(0)
36 | file.write(data.replace(seek, 'chrome.scripting.insertCSS'))
37 | file.truncate()
38 |
39 | if needsChange:
40 | if 'permissions' not in self.wrapper.manifest:
41 | self.wrapper.manifest['permissions'] = []
42 | permissions = self.wrapper.manifest['permissions']
43 | for permission in permissions:
44 | if permission == "scripting":
45 | needsChange = False
46 | break
47 |
48 | if needsChange:
49 | Logger().log("Adding scripting permission to manifest")
50 | self.wrapper.manifest['permissions'].append("scripting")
51 | self.writeManifest()
52 |
--------------------------------------------------------------------------------
/utils/unzip.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | import sys
13 | import zipfile
14 | import os
15 |
16 | class Unzip:
17 | def unzip(self, source, destination):
18 | with zipfile.ZipFile(source, 'r') as zip_ref:
19 | zip_ref.extractall(destination)
20 |
21 | def extract(self, source, destination, foldername):
22 | sentinel = False
23 | with zipfile.ZipFile(source, 'r') as zip_ref:
24 | list = zip_ref.namelist()
25 | if len(list) == 0:
26 | return
27 | for path in list:
28 | if path == "manifest.json":
29 | sentinel = True
30 | break
31 | if not sentinel:
32 | self.extractFirstDirectory(source, destination, foldername)
33 | else:
34 | outfile = destination + os.sep + foldername
35 | os.mkdir(outfile)
36 | for path in list:
37 | zip_ref.extract(path, destination + os.sep + foldername)
38 |
39 | def extractFirstDirectory(self, source, destination, foldername):
40 | with zipfile.ZipFile(source, 'r') as zip_ref:
41 | list = zip_ref.namelist()
42 | if len(list) == 0:
43 | return
44 | prefix = list[0]
45 | for x in list[1:]:
46 | if x.startswith(prefix):
47 | zip_ref.extract(x, destination)
48 | # TODO: Remove slash in path for OS independence.
49 | os.rename(destination + os.sep + prefix, destination + os.sep + foldername)
50 |
51 | def main():
52 | if len(sys.argv) != 2:
53 | return
54 | path = sys.argv[1]
55 | dirname = os.path.dirname(path)
56 | Unzip().unzip(path, dirname)
57 |
58 |
59 | if __name__ == '__main__':
60 | main()
61 |
--------------------------------------------------------------------------------
/src/modifiers/ExecuteScriptModifier.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from pathlib import Path
13 | import os
14 |
15 | from . import Modifier
16 | from . import Logger
17 |
18 | class ExecuteScriptModifier(Modifier):
19 | def _mv2(self):
20 | # TODO chrome.scripting.executeScript -> tab
21 | pass
22 |
23 | def _mv3(self):
24 | """
25 | from
26 |
27 | chrome.tabs.executeScript(
28 | null,
29 | {
30 | code: 'console.log("2 from code.");'
31 | }
32 | );
33 |
34 | to
35 |
36 | chrome.scripting.executeScript(
37 | {
38 | target: {
39 | tabId: tab.id
40 | },
41 | function: () => {}
42 | }
43 | );
44 |
45 | manifest.permissions += scripting
46 | """
47 |
48 | # Search all js files for "chrome.tabs.executeScript"
49 | needsChange = False
50 | for root, dirs, files in os.walk(self.wrapper.destination, topdown=False):
51 | for name in files:
52 | if str(Path(name).suffix) != '.js': continue
53 | path = root + os.sep + os.sep.join(dirs) + name
54 | if os.path.exists(path):
55 | with open(path, 'r+', encoding='UTF-8') as file:
56 | data = file.read()
57 | seek = 'chrome.tabs.executeScript'
58 | if data.find(seek) == -1: continue
59 | Logger().log("Updating to chrome.scripting.executeScript")
60 | if not needsChange: needsChange = True
61 | file.seek(0)
62 | file.write(data.replace(seek, 'chrome.scripting.executeScript'))
63 | file.truncate()
64 |
65 | if needsChange:
66 | if 'permissions' not in self.wrapper.manifest:
67 | self.wrapper.manifest['permissions'] = []
68 | permissions = self.wrapper.manifest['permissions']
69 | for permission in permissions:
70 | if permission == "scripting":
71 | needsChange = False
72 | break
73 |
74 | if needsChange:
75 | Logger().log("Adding scripting permission to manifest")
76 | self.wrapper.manifest['permissions'].append("scripting")
77 | self.writeManifest()
78 |
--------------------------------------------------------------------------------
/src/worker.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | import json
13 | import shutil
14 | import os
15 |
16 | from .logger import Logger
17 | from .wrapper import Wrapper
18 |
19 | from .modifiers.ManifestVersionModifier import ManifestVersionModifier
20 | from .modifiers.ServiceWorkerModifier import ServiceWorkerModifier
21 | from .modifiers.ManifestActionModifier import ManifestActionModifier
22 | from .modifiers.JSActionModifier import JSActionModifier
23 | from .modifiers.ExecuteScriptModifier import ExecuteScriptModifier
24 | from .modifiers.InsertCssModifier import InsertCssModifier
25 | from .modifiers.WebAccessibleResourcesModifier import WebAccessibleResourcesModifier
26 | from .modifiers.ContentSecurityPolicyModifier import ContentSecurityPolicyModifier
27 | from .modifiers.HostPermissionsModifier import HostPermissionsModifier
28 |
29 | class Worker:
30 | wrapper = None
31 |
32 | def work(self, source):
33 | Logger().log(source)
34 |
35 | # TODO: Can destination be a command line argument instead?
36 | destination = source + "_delete"
37 | if os.path.exists(destination):
38 | Logger().log("Overwriting already existing destination.", 1)
39 | # TODO: Should deletion be done by default?
40 | shutil.rmtree(destination)
41 |
42 | shutil.copytree(source, destination)
43 | manifest_path = destination + "/manifest.json"
44 | manifest = {}
45 | if not os.path.exists(manifest_path):
46 | Logger().log("Missing manifest", 0)
47 | else:
48 | if os.path.exists(manifest_path):
49 | with open(manifest_path) as json_file:
50 | manifest = json.load(json_file)
51 | self.wrapper = Wrapper(destination, manifest)
52 |
53 | [modifier.run() for modifier in [
54 | ManifestVersionModifier(self.wrapper),
55 | ServiceWorkerModifier(self.wrapper),
56 | ManifestActionModifier(self.wrapper),
57 | JSActionModifier(self.wrapper),
58 | ExecuteScriptModifier(self.wrapper),
59 | InsertCssModifier(self.wrapper),
60 | WebAccessibleResourcesModifier(self.wrapper),
61 | ContentSecurityPolicyModifier(self.wrapper),
62 | HostPermissionsModifier(self.wrapper),
63 | ]]
64 |
--------------------------------------------------------------------------------
/tests/test_convert/tabstourls_mv3/popup.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | document.addEventListener("DOMContentLoaded", ready);
15 |
16 | function ready() {
17 | document.getElementById("btnGetLinksText").addEventListener('click', () => {
18 | let div = document.getElementById('divResult');
19 | div.innerHTML = ' ';
20 | div.style.width = 600;
21 | chrome.tabs.query({currentWindow: true}, tabs => {
22 | tabs.forEach(tab => {
23 | div.innerHTML += tab.title + '. ' + tab.url + ' ';
24 | });
25 | });
26 | });
27 |
28 | document.getElementById("btnGetLinksHtml").addEventListener('click', () => {
29 | let div = document.getElementById('divResult');
30 | div.innerHTML = ' ';
31 | div.style.width = 600;
32 | chrome.tabs.query({currentWindow: true}, tabs => {
33 | tabs.forEach(tab => {
34 | div.innerHTML += '' + tab.title + ' ';
35 | });
36 | });
37 | });
38 |
39 | document.getElementById("btnCopyLinksText").addEventListener('click', () => {
40 | console.log('clicked2');
41 | let result = "";
42 | chrome.tabs.query({currentWindow: true}, tabs => {
43 | tabs.forEach(tab => {
44 | result += tab.title + '. ' + tab.url + '\n';
45 | });
46 | copyPlainTextToClipboard(result);
47 | });
48 | });
49 |
50 | document.getElementById("btnCopyLinksHtml").addEventListener('click', () => {
51 | console.log('clicked');
52 | let result = "";
53 | chrome.tabs.query({currentWindow: true}, tabs => {
54 | tabs.forEach(tab => {
55 | console.log(5);
56 | result += '' + tab.title + ' ';
57 | });
58 | copyRichTextToClipboard(result);
59 | });
60 | });
61 | }
62 |
63 | function copyRichTextToClipboard(text) {
64 | const listener = function(ev) {
65 | ev.preventDefault();
66 | ev.clipboardData.setData('text/html', text);
67 | ev.clipboardData.setData('text/plain', text); // is this line needed?
68 | };
69 | document.addEventListener('copy', listener);
70 | document.execCommand('copy');
71 | document.removeEventListener('copy', listener);
72 | }
73 |
74 | function copyPlainTextToClipboard(text) {
75 | const listener = function(ev) {
76 | ev.preventDefault();
77 | ev.clipboardData.setData('text/plain', text);
78 | };
79 | document.addEventListener('copy', listener);
80 | document.execCommand('copy');
81 | document.removeEventListener('copy', listener);
82 | }
83 |
--------------------------------------------------------------------------------
/src/frontend.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | from pathlib import Path
13 | import os
14 | import shutil
15 |
16 | from utils.unzip import Unzip
17 | from utils.zip import Zip
18 | from .type import Type, TypeEnum
19 | from .worker import Worker
20 | from .logger import Logger
21 |
22 | class Frontend:
23 | def __init__(self, args):
24 | source = args['source']
25 | original_source = source
26 | dirname = os.path.dirname(source)
27 |
28 | # TODO: If not a directory, create one and move this item into it.
29 | type = Type().getFileType(source)
30 |
31 | if type is TypeEnum.UNKNOWN:
32 | # TODO: Error
33 | return
34 | elif type is TypeEnum.ZIP:
35 | foldername = str(Path(os.path.basename(source)).with_suffix(''))
36 | destination = dirname + os.sep + foldername
37 | if os.path.exists(destination):
38 | os.remove(destination)
39 | Unzip().extract(source, dirname, foldername)
40 | source = str(Path(source).with_suffix(''))
41 | elif type != TypeEnum.DIR:
42 | source_dir = source + '_dir'
43 | os.mkdir(source_dir)
44 | os.rename(source, source_dir + os.sep + os.path.basename(source))
45 | source = source_dir
46 |
47 | worker = Worker()
48 | worker.work(source)
49 |
50 | # Maybe remove working or source directory.
51 | # Destination directories are created alongside source with _delete
52 | # appended.
53 | if type is TypeEnum.ZIP:
54 | Logger().log("Replacing .zip file")
55 |
56 | # Create zip for working directory.
57 | prev_source = source
58 | source = source + '_delete'
59 | os.mkdir(dirname + '/tmp_delete')
60 | shutil.move(source, dirname + '/tmp_delete/extension/')
61 | Zip().zip(dirname + '/tmp_delete', source)
62 |
63 | # Delete working directory.
64 | shutil.rmtree(dirname + '/tmp_delete')
65 | shutil.rmtree(prev_source)
66 | os.remove(original_source)
67 | os.rename(source + '.zip', original_source)
68 | # A manifest.json file replaces the original.
69 | # TODO: Consistent replacement behavior with dir, zip, and manifest.json.
70 | elif type is TypeEnum.MANIFEST:
71 | Logger().log("Replacing manifest.json")
72 | os.remove(source + os.sep + os.path.basename(original_source))
73 | os.rmdir(source)
74 | source = source + '_delete/'
75 | os.rename(source + os.path.basename(original_source), original_source)
76 | os.rmdir(source)
77 | elif type is TypeEnum.DIR:
78 | # TODO: A better destination could be something other than _delete.
79 | Logger().log("Converted extension into: {}_delete".format(source))
80 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Extension Manifest Converter
2 |
3 | Extension Manifest Converter is an open source tool that helps convert existing Chrome extensions to
4 | Manifest V3. Use it to convert:
5 |
6 | - an entire unpacked directory
7 | - an extension zip file
8 | - just a manifest.json file.
9 |
10 | After using the tool, complete the conversion using instructions in the [Migrate to Manifest
11 | V3](https://developer.chrome.com/docs/extensions/migrating/) guide. This tool makes the conversions
12 | listed below. To make completion of the upgrading easier, the titles and bullets below roughly
13 | correspond to the wording of the headings and items in
14 | [the migration guide's checklist](https://developer.chrome.com/docs/extensions/migrating/checklist/).
15 |
16 | ## Updates to the manifest
17 |
18 | - Changes the manifest version number.
19 | - Updates the host permissions.
20 |
21 | ## Updates that migrate to a service worker
22 |
23 | - Upgrades the `"background"` field in the manifest.
24 |
25 | ## Updates to API calls
26 |
27 | - Replaces `tabs.executeScript()` with `scripting.executeScript()`. (If necessary, also adds
28 | `scripting` to the `permissions` array in the `manifest.json`.)
29 | - Replaces `tabs.insertCSS()` with `scripting.insertCSS()`. You will still need to
30 | [replace `tabs.removeCSS()` with `scripting.removeCSS()](https://developer.chrome.com/docs/extensions/migrating/api-calls/#replace-insertcss-removecss)`.
31 | (If necessary, also adds `scripting` to the `permissions` array in manifest.json.)
32 | - Replaces Browser Actions and Page Actions with Actions and makes related changes to the manifest.
33 |
34 | ## Improvement to extension security
35 |
36 | - Updates the content security policy.
37 |
38 | ## Limitations
39 |
40 | This tool aims to simplify the MV3 conversion; it does not fully automate the process. Only search
41 | and replace changes are applied to `.js` files.
42 |
43 | This tool does not:
44 |
45 | * update any service worker code that relies on the DOM.
46 |
47 | ## Installation
48 |
49 | To use this tool, follow the steps below.
50 |
51 | 1. Make sure Python 3 is installed.
52 |
53 | ```bash
54 | python3 --version
55 | ```
56 |
57 | If you don't see a version number, follow your OS's guidance to install Python 3 or visit
58 | https://www.python.org/downloads/ to download a recent release.
59 |
60 | 2. Clone this repo using the below command.
61 |
62 | ```bash
63 | git clone https://github.com/GoogleChromeLabs/extension-manifest-converter
64 | ```
65 |
66 | 3. `cd` into the cloned project directory.
67 |
68 | 4. Execute the test command.
69 |
70 | ```bash
71 | python3 emc.py
72 | ```
73 |
74 | The tool logs basic usage information to the console.
75 |
76 |
77 | ## Usage
78 |
79 | * Convert a directory
80 |
81 | ```bash
82 | python3 emc.py dir/path/
83 | ```
84 |
85 | * Convert a manifest file
86 |
87 | ```bash
88 | python3 emc.py manifest.json
89 | ```
90 |
91 | * Convert a .zip file
92 |
93 | ```bash
94 | python3 emc.py extension.zip
95 | ```
96 |
97 | ## License
98 | [Apache 2.0](https://github.com/GoogleChromeLabs/extension-manifest-converter/blob/master/LICENSE)
99 |
100 | This is not an official Google product.
101 |
--------------------------------------------------------------------------------
/tests/test_convert/todolist_mv3/popup.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | document.addEventListener('DOMContentLoaded', setup, false);
15 |
16 | let key = "todoListItems";
17 | var items = {};
18 |
19 | function getFontSize() {
20 | chrome.storage.local.get({
21 | todoListFontSize: '90'
22 | }, function(items) {
23 | document.body.style.fontSize = items.todoListFontSize + '%';
24 | });
25 | }
26 |
27 | var port;
28 |
29 | function setup() {
30 | port = chrome.runtime.connect({ name: "popup" });
31 | port.onMessage.addListener(function(msg) {
32 | let element = document.getElementById("addText");
33 | element.value = msg.text;
34 | });
35 |
36 | getFontSize();
37 | getItems();
38 | let element = document.getElementById("addText");
39 | element.focus();
40 | element.addEventListener("keyup", event => {
41 | if (event.key === 'Enter') {
42 | addItem();
43 | }
44 | });
45 | element.addEventListener("input", event => {
46 | let element = document.getElementById("addText");
47 | port.postMessage({text: element.value});
48 | });
49 | }
50 |
51 | function addItem() {
52 | let addItemElement = document.getElementById("addText");
53 | let text = addItemElement.value.slice(0, -1);
54 | if (text === ''|| text == null || text == undefined) {
55 | addItemElement.value = "";
56 | return;
57 | }
58 | document.getElementById("todoList").innerHTML += text + ' ';
59 | addItemElement.value = "";
60 | items[generateKey(text)] = text;
61 | save();
62 | port.postMessage({text: ""});
63 | }
64 |
65 | function hash(str) {
66 | return str.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
67 | }
68 |
69 | function generateKey(text) {
70 | return `${Date.now().toString()}.${hash(text)}`;
71 | }
72 |
73 | function getItems() {
74 | chrome.storage.local.get([key], function(result) {
75 | if (!result[key]) return;
76 | items = JSON.parse(result[key]);
77 | document.getElementById("todoList").innerHTML = "";
78 | for (var item in items) {
79 | let todoList = document.getElementById("todoList");
80 | let div = document.createElement('div');
81 | let button = document.createElement('button');
82 | button.setAttribute('id', item);
83 | button.setAttribute('class', 'button');
84 | button.innerHTML = '✓';
85 | button.addEventListener('click', (event) => removeItem(event));
86 | let clipboardButton = document.createElement('button');
87 | clipboardButton.innerHTML = '⧉';
88 | clipboardButton.setAttribute('class', 'margin-right-half button');
89 | addListenerForClipboardButton(clipboardButton, items[item]);
90 | div.append(button);
91 | div.append(clipboardButton);
92 | div.append(items[item]);
93 | todoList.append(div);
94 | }
95 | });
96 | }
97 |
98 | function removeItem(event) {
99 | delete items[event.srcElement.id];
100 | save();
101 | }
102 |
103 | function save() {
104 | let elements = JSON.stringify(items);
105 | chrome.storage.local.set({[key]: elements}, () => {});
106 | getItems();
107 | }
108 |
109 | function addListenerForClipboardButton(button, value) {
110 | button.addEventListener("click", () => {
111 | let tmp = document.createElement("textarea");
112 | tmp.value = value;
113 | tmp.style.height = "0";
114 | tmp.style.overflow = "hidden";
115 | tmp.style.position = "fixed";
116 | document.body.appendChild(tmp);
117 | tmp.focus();
118 | tmp.select();
119 | document.execCommand("copy");
120 | document.body.removeChild(tmp);
121 | });
122 | }
123 |
--------------------------------------------------------------------------------
/tests/test_convert/backgroundScripts_mv2/popup.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | document.addEventListener('DOMContentLoaded', setup, false);
15 |
16 | let key = "todoListItems";
17 | var items = {};
18 |
19 | function getFontSize() {
20 | chrome.storage.local.get({
21 | todoListFontSize: '90'
22 | }, function(items) {
23 | document.body.style.fontSize = items.todoListFontSize + '%';
24 | });
25 | }
26 |
27 | var port;
28 |
29 | function setup() {
30 | port = chrome.runtime.connect({ name: "popup" });
31 | port.onMessage.addListener(function(msg) {
32 | let element = document.getElementById("addText");
33 | element.value = msg.text;
34 | });
35 |
36 | getFontSize();
37 | getItems();
38 | let element = document.getElementById("addText");
39 | element.focus();
40 | element.addEventListener("keyup", event => {
41 | if (event.key === 'Enter') {
42 | addItem();
43 | }
44 | });
45 | element.addEventListener("input", event => {
46 | let element = document.getElementById("addText");
47 | port.postMessage({text: element.value});
48 | });
49 | }
50 |
51 | function addItem() {
52 | let addItemElement = document.getElementById("addText");
53 | let text = addItemElement.value.slice(0, -1);
54 | if (text === ''|| text == null || text == undefined) {
55 | addItemElement.value = "";
56 | return;
57 | }
58 | document.getElementById("todoList").innerHTML += text + ' ';
59 | addItemElement.value = "";
60 | items[generateKey(text)] = text;
61 | save();
62 | port.postMessage({text: ""});
63 | }
64 |
65 | function hash(str) {
66 | return str.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
67 | }
68 |
69 | function generateKey(text) {
70 | return `${Date.now().toString()}.${hash(text)}`;
71 | }
72 |
73 | function getItems() {
74 | chrome.storage.local.get([key], function(result) {
75 | if (!result[key]) return;
76 | items = JSON.parse(result[key]);
77 | document.getElementById("todoList").innerHTML = "";
78 | for (var item in items) {
79 | let todoList = document.getElementById("todoList");
80 | let div = document.createElement('div');
81 | let button = document.createElement('button');
82 | button.setAttribute('id', item);
83 | button.setAttribute('class', 'button');
84 | button.innerHTML = '✓';
85 | button.addEventListener('click', (event) => removeItem(event));
86 | let clipboardButton = document.createElement('button');
87 | clipboardButton.innerHTML = '⧉';
88 | clipboardButton.setAttribute('class', 'margin-right-half button');
89 | addListenerForClipboardButton(clipboardButton, items[item]);
90 | div.append(button);
91 | div.append(clipboardButton);
92 | div.append(items[item]);
93 | todoList.append(div);
94 | }
95 | });
96 | }
97 |
98 | function removeItem(event) {
99 | delete items[event.srcElement.id];
100 | save();
101 | }
102 |
103 | function save() {
104 | let elements = JSON.stringify(items);
105 | chrome.storage.local.set({[key]: elements}, () => {});
106 | getItems();
107 | }
108 |
109 | function addListenerForClipboardButton(button, value) {
110 | button.addEventListener("click", () => {
111 | let tmp = document.createElement("textarea");
112 | tmp.value = value;
113 | tmp.style.height = "0";
114 | tmp.style.overflow = "hidden";
115 | tmp.style.position = "fixed";
116 | document.body.appendChild(tmp);
117 | tmp.focus();
118 | tmp.select();
119 | document.execCommand("copy");
120 | document.body.removeChild(tmp);
121 | });
122 | }
123 |
--------------------------------------------------------------------------------
/tests/test_convert.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google Inc. All Rights Reserved.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | # http://www.apache.org/licenses/LICENSE-2.0
6 | # Unless required by applicable law or agreed to in writing, software
7 | # distributed under the License is distributed on an "AS IS" BASIS,
8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
12 | import unittest
13 | import os
14 | import shutil
15 |
16 | from src.worker import Worker
17 |
18 | class TestConvert(unittest.TestCase):
19 | cwd = os.path.dirname(os.path.abspath(__file__))
20 | source = cwd + os.sep + 'test_convert' + os.sep
21 | destination = ''
22 |
23 | def setUp(self):
24 | pass
25 |
26 | def tearDown(self):
27 | pass
28 |
29 | def test_mv3_to_mv2_A(self):
30 | worker = Worker()
31 | self.source += 'todolist_mv3'
32 | self.destination = self.source + '_delete'
33 | worker.work(self.source)
34 |
35 | expected = 2
36 | actual = worker.wrapper.getManifestVersion()
37 | self.assertEqual(actual, expected, 'manifest_version')
38 |
39 | manifest = worker.wrapper.manifest
40 | self.assertIn('background', manifest)
41 | self.assertIn('scripts', manifest['background'])
42 |
43 | shutil.rmtree(self.destination)
44 |
45 | def test_mv3_to_mv2_B(self):
46 | worker = Worker()
47 | self.source += 'tabstourls_mv3'
48 | self.destination = self.source + '_delete'
49 | worker.work(self.source)
50 |
51 | expected = 2
52 | actual = worker.wrapper.getManifestVersion()
53 | self.assertEqual(actual, expected, 'manifest_version')
54 |
55 | shutil.rmtree(self.destination)
56 |
57 | def test_mv3_to_mv2_C(self):
58 | worker = Worker()
59 | self.source += 'timebadge_mv3'
60 | self.destination = self.source + '_delete'
61 | worker.work(self.source)
62 |
63 | expected = 2
64 | actual = worker.wrapper.getManifestVersion()
65 | self.assertEqual(actual, expected, 'manifest_version')
66 |
67 | manifest = worker.wrapper.manifest
68 | self.assertIn('background', manifest)
69 | self.assertIn('scripts', manifest['background'])
70 |
71 | shutil.rmtree(self.destination)
72 |
73 | def test_mv2_C(self):
74 | worker = Worker()
75 | self.source += 'backgroundScripts_mv2'
76 | self.destination = self.source + '_delete'
77 | worker.work(self.source)
78 |
79 | expected = 3
80 | actual = worker.wrapper.getManifestVersion()
81 | self.assertEqual(actual, expected, 'manifest_version')
82 |
83 | manifest = worker.wrapper.manifest
84 | self.assertIn('background', manifest)
85 | self.assertIn('service_worker', manifest['background'])
86 | self.assertEqual(manifest['background']['service_worker'], 'service_worker.js')
87 | self.assertFalse(os.path.exists(worker.wrapper.destination + os.sep + 'script1.js'))
88 | self.assertFalse(os.path.exists(worker.wrapper.destination + os.sep + 'script2.js'))
89 |
90 | shutil.rmtree(self.destination)
91 |
92 | def test23executeScript(self):
93 | worker = Worker()
94 | self.source += 'test23executeScript'
95 | self.destination = self.source + '_delete'
96 | worker.work(self.source)
97 |
98 | expected = 3
99 | actual = worker.wrapper.getManifestVersion()
100 | self.assertEqual(actual, expected, 'manifest_version')
101 |
102 | manifest = worker.wrapper.manifest
103 | self.assertIn('background', manifest)
104 | self.assertIn('service_worker', manifest['background'])
105 | self.assertEqual(manifest['background']['service_worker'], 'service_worker.js')
106 |
107 | self.assertIn('permissions', manifest)
108 | self.assertIn('scripting', manifest['permissions'])
109 |
110 | shutil.rmtree(self.destination)
111 |
112 |
113 | def test23missingPermissions(self):
114 | worker = Worker()
115 | self.source += 'test23missingPermissions'
116 | self.destination = self.source + '_delete'
117 | worker.work(self.source)
118 |
119 | expected = 3
120 | actual = worker.wrapper.getManifestVersion()
121 | self.assertEqual(actual, expected, 'manifest_version')
122 |
123 | manifest = worker.wrapper.manifest
124 | self.assertIn('background', manifest)
125 | self.assertIn('service_worker', manifest['background'])
126 | self.assertEqual(manifest['background']['service_worker'], 'service_worker.js')
127 |
128 | self.assertIn('permissions', manifest)
129 | self.assertIn('scripting', manifest['permissions'])
130 |
131 | shutil.rmtree(self.destination)
132 |
133 | def test23webAccessibleResources(self):
134 | worker = Worker()
135 | self.source += 'test23webAccessibleResources'
136 | self.destination = self.source + '_delete'
137 | worker.work(self.source)
138 |
139 | expected = 3
140 | actual = worker.wrapper.getManifestVersion()
141 | self.assertEqual(actual, expected, 'manifest_version')
142 |
143 | manifest = worker.wrapper.manifest
144 | self.assertIn('background', manifest)
145 | self.assertIn('service_worker', manifest['background'])
146 | self.assertEqual(manifest['background']['service_worker'], 'service_worker.js')
147 |
148 | self.assertIn('permissions', manifest)
149 | self.assertIn('scripting', manifest['permissions'])
150 |
151 | key = 'web_accessible_resources'
152 | self.assertIn(key, manifest)
153 | self.assertEqual(len(manifest[key][0]['resources']), 2)
154 |
155 | shutil.rmtree(self.destination)
156 |
157 | def test23contentSecurityPolicy(self):
158 | worker = Worker()
159 | self.source += 'test23contentSecurityPolicy'
160 | self.destination = self.source + '_delete'
161 | worker.work(self.source)
162 |
163 | expected = 3
164 | actual = worker.wrapper.getManifestVersion()
165 | self.assertEqual(actual, expected, 'manifest_version')
166 |
167 | manifest = worker.wrapper.manifest
168 | key = 'content_security_policy'
169 | self.assertIn(key, manifest)
170 | self.assertIn('extension_pages', manifest[key])
171 | self.assertIn('sandbox', manifest[key])
172 |
173 | shutil.rmtree(self.destination)
174 |
175 | def test23hostPermissions(self):
176 | worker = Worker()
177 | self.source += 'test23hostPermissions'
178 | self.destination = self.source + '_delete'
179 | worker.work(self.source)
180 |
181 | expected = 3
182 | actual = worker.wrapper.getManifestVersion()
183 | self.assertEqual(actual, expected, 'manifest_version')
184 |
185 | manifest = worker.wrapper.manifest
186 |
187 | key = 'permissions'
188 | self.assertIn(key, manifest)
189 | self.assertEqual(len(manifest[key]), 2)
190 |
191 | key = 'optional_permissions'
192 | self.assertIn(key, manifest)
193 | self.assertEqual(len(manifest[key]), 1)
194 |
195 | key = 'host_permissions'
196 | self.assertIn(key, manifest)
197 | self.assertEqual(len(manifest[key]), 2)
198 |
199 | shutil.rmtree(self.destination)
200 |
201 | def test23simple(self):
202 | worker = Worker()
203 | self.source += 'test23simple'
204 | self.destination = self.source + '_delete'
205 | worker.work(self.source)
206 |
207 | expected = 3
208 | actual = worker.wrapper.getManifestVersion()
209 | self.assertEqual(actual, expected, 'manifest_version')
210 |
211 | manifest = worker.wrapper.manifest
212 |
213 | key = 'optional_permissions'
214 | self.assertNotIn(key, manifest)
215 |
216 | key = 'host_permissions'
217 | self.assertNotIn(key, manifest)
218 |
219 | shutil.rmtree(self.destination)
220 |
221 | if __name__ == '__main__':
222 | unittest.main()
223 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------