├── __init__.py ├── src ├── __init__.py ├── modifiers │ ├── __init__.py │ ├── ManifestVersionModifier.py │ ├── WebAccessibleResourcesModifier.py │ ├── Modifier.py │ ├── ManifestActionModifier.py │ ├── ContentSecurityPolicyModifier.py │ ├── HostPermissionsModifier.py │ ├── JSActionModifier.py │ ├── ServiceWorkerModifier.py │ ├── InsertCssModifier.py │ └── ExecuteScriptModifier.py ├── convert.py ├── logger.py ├── type.py ├── wrapper.py ├── worker.py └── frontend.py ├── tests ├── __init__.py ├── test_type │ ├── a.zip │ ├── unknown │ ├── dir │ │ └── FILE │ └── manifest.json ├── test_convert │ ├── test23webAccessibleResources │ │ ├── 2.html │ │ ├── 1.html │ │ ├── manifest.json │ │ └── background.js │ ├── test32webAccessibleResources │ │ ├── 2.html │ │ ├── 1.html │ │ ├── manifest.json │ │ └── service_worker.js │ ├── tabstourls_mv3 │ │ ├── 128.gif │ │ ├── popup.css │ │ ├── manifest.json │ │ ├── popup.html │ │ └── popup.js │ ├── timebadge_mv3 │ │ ├── time.png │ │ ├── manifest.json │ │ └── worker.js │ ├── todolist_mv3 │ │ ├── icon.png │ │ ├── popup.html │ │ ├── manifest.json │ │ ├── options.html │ │ ├── popup.css │ │ ├── service_worker.js │ │ ├── options.js │ │ └── popup.js │ ├── backgroundScripts_mv2 │ │ ├── icon.png │ │ ├── popup.html │ │ ├── manifest.json │ │ ├── popup.css │ │ ├── script1.js │ │ ├── script2.js │ │ └── popup.js │ ├── test23simple │ │ └── manifest.json │ ├── test23missingPermissions │ │ ├── manifest.json │ │ └── background.js │ ├── test23executeScript │ │ ├── manifest.json │ │ ├── script.js │ │ └── background.js │ ├── test23hostPermissions │ │ └── manifest.json │ └── test23contentSecurityPolicy │ │ └── manifest.json ├── test_zip │ └── manifest.json.zip ├── test_manifest.json │ └── manifest.json ├── test_type.py └── test_convert.py ├── .gitignore ├── TODO ├── emc.py ├── test.py ├── utils ├── zip.py ├── arguments.py └── unzip.py ├── CONTRIBUTING.md ├── README.md └── LICENSE /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_type/a.zip: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_type/unknown: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_type/dir/FILE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_type/manifest.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *__pycache__ 2 | *\.pyc 3 | *\.DS_Store 4 | -------------------------------------------------------------------------------- /tests/test_convert/test23webAccessibleResources/2.html: -------------------------------------------------------------------------------- 1 | Hi from 2.html 2 | -------------------------------------------------------------------------------- /tests/test_convert/test32webAccessibleResources/2.html: -------------------------------------------------------------------------------- 1 | Hi from 2.html 2 | -------------------------------------------------------------------------------- /tests/test_convert/test23webAccessibleResources/1.html: -------------------------------------------------------------------------------- 1 | Hi from 1.html. 2 | -------------------------------------------------------------------------------- /tests/test_convert/test32webAccessibleResources/1.html: -------------------------------------------------------------------------------- 1 | Hi from 1.html. 2 | -------------------------------------------------------------------------------- /tests/test_zip/manifest.json.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/extension-manifest-converter/main/tests/test_zip/manifest.json.zip -------------------------------------------------------------------------------- /tests/test_convert/tabstourls_mv3/128.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/extension-manifest-converter/main/tests/test_convert/tabstourls_mv3/128.gif -------------------------------------------------------------------------------- /tests/test_convert/timebadge_mv3/time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/extension-manifest-converter/main/tests/test_convert/timebadge_mv3/time.png -------------------------------------------------------------------------------- /tests/test_convert/todolist_mv3/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/extension-manifest-converter/main/tests/test_convert/todolist_mv3/icon.png -------------------------------------------------------------------------------- /tests/test_convert/backgroundScripts_mv2/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/extension-manifest-converter/main/tests/test_convert/backgroundScripts_mv2/icon.png -------------------------------------------------------------------------------- /tests/test_convert/test23simple/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test23simple", 3 | "description": "", 4 | "author": "Solomon Kinard", 5 | "incognito": "split", 6 | "manifest_version": 2, 7 | "version": "1.0", 8 | "permissions": [ 9 | "tabs" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tests/test_convert/todolist_mv3/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /tests/test_convert/backgroundScripts_mv2/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 |
10 | 11 | -------------------------------------------------------------------------------- /tests/test_convert/test23missingPermissions/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test2missingPermissions", 3 | "description": "", 4 | "author": "Solomon Kinard", 5 | "incognito": "split", 6 | "manifest_version": 2, 7 | "version": "1.0", 8 | "background": { 9 | "scripts": ["background.js"] 10 | }, 11 | "browser_action": {} 12 | } 13 | -------------------------------------------------------------------------------- /tests/test_convert/tabstourls_mv3/popup.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg-color: #303030; 3 | --main-fg-color: white; 4 | } 5 | 6 | body { 7 | background-color: var(--main-bg-color); 8 | color: var(--main-fg-color); 9 | } 10 | 11 | button { 12 | background-color: var(--main-bg-color); 13 | color: var(--main-fg-color); 14 | } 15 | 16 | a { 17 | color: white; 18 | } -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | # Don't throw an exception (e.g. file not found). 2 | # extension.py -d for dry run to only show logs of what would happen. 3 | # extension.py -o . 4 | # extension.py -l . 5 | # ExecuteScriptModifier: Also update content instead of just search and replace. 6 | # service_worker.js: Allow name to be specified on command line. 7 | # Consider using argparse. 8 | -------------------------------------------------------------------------------- /tests/test_convert/tabstourls_mv3/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tabs To Urls", 3 | "description": "", 4 | "version": "1.0", 5 | "manifest_version": 3, 6 | "action": { 7 | "default_popup": "popup.html" 8 | }, 9 | "permissions": [ 10 | "tabs", 11 | "clipboardWrite" 12 | ], 13 | "icons": { "128": "128.gif" }, 14 | "author": "solomonkinard" 15 | } 16 | -------------------------------------------------------------------------------- /tests/test_convert/test23executeScript/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test23executeScript", 3 | "description": "Text converting executeScript from MV2 to MV3.", 4 | "author": "Solomon Kinard", 5 | "incognito": "split", 6 | "manifest_version": 2, 7 | "version": "1.0", 8 | "permissions": ["activeTab"], 9 | "background": { 10 | "scripts": ["background.js"] 11 | }, 12 | "browser_action": {} 13 | } 14 | -------------------------------------------------------------------------------- /tests/test_convert/test23hostPermissions/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test23hostPermissions", 3 | "description": "", 4 | "author": "Solomon Kinard", 5 | "incognito": "split", 6 | "manifest_version": 2, 7 | "version": "1.0", 8 | "permissions": [ 9 | "tabs", 10 | "bookmarks", 11 | "http://www.blogger.com/" 12 | ], 13 | "optional_permissions": [ 14 | "*://*/*", 15 | "unlimitedStorage" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tests/test_convert/timebadge_mv3/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Time Badge", 3 | "description": "Time now visible on the extension icon when clicked.", 4 | "author": "Solomon Kinard", 5 | "icons": {"128": "time.png"}, 6 | "incognito": "split", 7 | "manifest_version": 3, 8 | "version": "1.0", 9 | "action": {}, 10 | "permissions": ["alarms"], 11 | "background": { 12 | "service_worker": "worker.js" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/test_convert/test23webAccessibleResources/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test23webAccessibleResources", 3 | "description": "", 4 | "author": "Solomon Kinard", 5 | "incognito": "split", 6 | "manifest_version": 2, 7 | "version": "1.0", 8 | "web_accessible_resources": [ 9 | "1.html", 10 | "2.html" 11 | ], 12 | "browser_action": {}, 13 | "permissions": ["activeTab"], 14 | "background": { 15 | "scripts": ["background.js"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/test_manifest.json/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "update_url": "https://clients2.google.com/service/update2/crx", 3 | "manifest_version": 2, 4 | "name": "Test name", 5 | "description": "Test description.", 6 | "version": "2.1", 7 | "browser_action": { 8 | "default_icon": "icon.png", 9 | "default_popup": "index.html" 10 | }, 11 | "icons": { 12 | "16": "icon.png", 13 | "32": "icon.png", 14 | "48": "icon.png", 15 | "128": "icon.png" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/test_convert/tabstourls_mv3/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 |
10 |
11 | 12 | 13 |
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 | --------------------------------------------------------------------------------