├── .gitignore
├── magnify.png
├── screenshot.png
├── manifest.json
├── popup.css
├── popup.html
├── README.md
├── presets.json
├── popup.js
└── lib
└── browser-polyfill.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .web-extension-id
2 | web-ext-artifacts
--------------------------------------------------------------------------------
/magnify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevgk/OD-Search/HEAD/magnify.png
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevgk/OD-Search/HEAD/screenshot.png
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "OD-Search",
4 | "version": "1.2",
5 | "description": "Web Extension that builds search queries.",
6 | "browser_action": {
7 | "default_title": "OD-Search",
8 | "default_icon": "magnify.png",
9 | "default_popup": "popup.html"
10 | },
11 | "permissions": [
12 | "storage",
13 | "clipboardWrite"
14 | ],
15 | "applications": {
16 | "gecko": {
17 | "id": "{bda7d743-06f5-4628-bcf7-7a847293a0b3}"
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/popup.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 |
6 | html, body {
7 | width: 300px;
8 | font-family: sans-serif;
9 | display: flex;
10 | align-items: center;
11 | }
12 |
13 | label {
14 | font-size: 12px;
15 | padding: 8px 0;
16 | }
17 |
18 | .container {
19 | display: flex;
20 | flex-wrap: wrap;
21 | width: 100%;
22 | margin: 0 auto;
23 | padding: 5px;
24 | }
25 |
26 | .search__input,
27 | .search__button {
28 | padding: 5px 10px;
29 | }
30 |
31 | .search__input {
32 | background-color: #fff;
33 | outline: none;
34 | border: 1px solid #ccc;
35 | width: 100%;
36 | }
37 |
38 | input, select, button {
39 | height: 30px;
40 | margin-bottom: 10px;
41 | }
42 |
43 | .search__button {
44 | color: #fff;
45 | border: none;
46 | background-color: #5e92f3;
47 | text-align: center;
48 | width: 50%;
49 | cursor: pointer;
50 | transition: background-color .3s;
51 | }
52 |
53 | .search__button:hover {
54 | background-color: #003c8f;
55 | }
56 |
57 | .secondary {
58 | background-color: #5472d3;
59 | }
60 |
61 | .secondary:hover {
62 | background-color: #002171;
63 | }
64 |
65 | .search__select,
66 | .search__exclude,
67 | .search__exclude > input {
68 | width: 100%;
69 | }
70 |
71 |
72 | .footer {
73 | width: 100%;
74 | text-align: center;
75 | display: block;
76 | font-size: 10px;
77 | text-decoration: none;
78 | color: #000;
79 | }
--------------------------------------------------------------------------------
/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OD-Search
2 | > Find files on the Internet.
3 |
4 | OD-Search builds search queries, to find files on the Internet. Just select a preset (eg. videos, documents, audiofiles, ...) enter a searchterm and go.
5 |
6 | 
7 |
8 | ## Installation
9 | Firefox:
10 | [OD-Search on Firefox Addons](https://addons.mozilla.org/de/firefox/addon/od-search/)
11 |
12 | ## About The Project
13 | Why OD-Search and not one of the many websites:
14 | * No middleman / 3rd Parties like CDNs
15 | * Not depending on someone elses server
16 | * No analytics / ads / logging
17 | * Convinience
18 | * Extendable (soon)
19 |
20 | ## Getting Started
21 | After you have installed the addon, you should see a search icon in your browser addons. Click on the search icon to open the search window.
22 |
23 | Now all you have to do is enter your search term, select a preset and click search.
24 |
25 | You can also copy the search query to your clipboard, if you don't want to search directly, just click on "copy query".
26 |
27 | The default search engine is Google, but you can customize it by clicking on the search engine menu. Currently we support Google, Startpage, DuckDuckGo and SearX.
28 |
29 | If you only want to get results for a certain timeframe, you can select it from the Timemenu. (We currently only support this feature on Google)
30 |
31 | You can also filter urls and words from the results by entering the urls/words in the corresponding input field. Multiple Urls/Words must be separated by a comma.
32 |
33 | ## How does it work
34 | All this addon does is combine search parameters.
35 |
36 | Here for we have developed a preset system that makes it easy to create new presets and extend old ones.
37 |
38 | For example, we have a preset for video formats and a preset for searches in Google Drive, now we combine them and have a Google Drive Video preset.
39 |
40 | We also have a preset that filters results from pages that intentionally deceive search results. Like a blacklist.
41 |
42 | Now we can include the blacklist in our presets and improve our search results.
43 |
44 | ## Presets
45 | Currently, presets can only be added/adjusted by the developer through updates.
46 |
47 | But this will change soon, we are working on a preset manager that allows you to easily add new presets.
48 |
49 | We also hope that the community will share their presets so that everyone can benefit.
50 |
51 | ## Contributing
52 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
53 |
54 | 1. Fork the Project
55 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
56 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
57 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
58 | 5. Open a Pull Request
59 |
60 | ## Contact
61 | If you have a problem, suggestions, wishes or feedback, please open a pull-request.
62 |
63 | ## Disclaimer
64 | We do not support illegal activities. We only help to make information more easily accessible on the Internet.
65 |
66 | ## Acknowledgements
67 | * [Lumpysoft](https://github.com/level42ca/lumpysoft)
68 | * [opendirectory-finder](https://github.com/ewasion/opendirectory-finder)
--------------------------------------------------------------------------------
/presets.json:
--------------------------------------------------------------------------------
1 | {
2 | "videos": {
3 | "title": "Videos",
4 | "include": ["videoFormats", "commons"],
5 | "hidden": false
6 | },
7 | "gdrive_videos": {
8 | "title": "Videos (Google Drive)",
9 | "content": ["site:drive.google.com"],
10 | "include": ["videoFormats"]
11 | },
12 | "images": {
13 | "title": "Images",
14 | "searchTerm": {
15 | "prepend": "",
16 | "append": ""
17 | },
18 | "include": ["imageFormats", "commons"],
19 | "hidden": false
20 | },
21 | "gdrive_images": {
22 | "title": "Images (Google Drive)",
23 | "content": ["site:drive.google.com"],
24 | "include": ["imageFormats"]
25 | },
26 | "imagesWpUploads": {
27 | "title": "Images (wp_uploads)",
28 | "content": ["inurl:\"/wp-content/uploads/\""],
29 | "include": ["imageFormats", "commons"]
30 | },
31 | "audio": {
32 | "title": "Audio",
33 | "include": ["audioFormats", "commons"],
34 | "hidden": false
35 | },
36 | "gdrive_audio": {
37 | "title": "Audio (Google Drive)",
38 | "content": ["site:drive.google.com"],
39 | "include": ["audioFormats"]
40 | },
41 | "documents": {
42 | "title": "Documents",
43 | "include": ["documentFormats", "commons"],
44 | "hidden": false
45 | },
46 | "gdrive_documents": {
47 | "title": "Documents (Google Drive)",
48 | "content": ["site:drive.google.com"],
49 | "include": ["documentFormats"]
50 | },
51 | "documentsWpUploads": {
52 | "title": "Documents (wp_uploads)",
53 | "content": ["inurl:\"/wp-content/uploads/\""],
54 | "include": ["documentFormats", "commons"]
55 | },
56 | "executables": {
57 | "title": "Executables",
58 | "include": ["executableFormats", "commons"],
59 | "hidden": false
60 | },
61 | "archives": {
62 | "title": "Archives",
63 | "include": ["archiveFormats", "commons"],
64 | "hidden": false
65 | },
66 | "gdrive_archives": {
67 | "title": "Archives (Google Drive)",
68 | "content": ["site:drive.google.com"],
69 | "include": ["archiveFormats"]
70 | },
71 | "videoFormats": {
72 | "content": ["(avi|mkv|mov|mp4|mpg|wmv)"],
73 | "hidden": true
74 | },
75 | "audioFormats": {
76 | "content": ["(ac3|flac|m4a|mp3|ogg|wav|wma)"],
77 | "hidden": true
78 | },
79 | "documentFormats": {
80 | "content": ["(CBZ|CBR|CHM|DOC|DOCX|EPUB|MOBI|ODT|PDF|RTF|txt)"],
81 | "hidden": true
82 | },
83 | "imageFormats": {
84 | "content": ["(bmp|gif|jpg|jpeg|png|psd|tif|tiff)"],
85 | "hidden": true
86 | },
87 | "executableFormats": {
88 | "content": ["(exe|apk)"],
89 | "hidden": true
90 | },
91 | "archiveFormats": {
92 | "content": ["(iso|rar|tar|zip|7z)"],
93 | "hidden": true
94 | },
95 | "commons": {
96 | "content": [
97 | "-inurl:(jsp|pl|php|html|aspx|htm|cf|shtml)",
98 | "-inurl:(index_of|listen77|mp3raid|mp3toss|mp3drug|index_of|wallywashis)",
99 | "intitle:\"index.of.\""
100 | ],
101 | "hidden": true
102 | },
103 | "indexof": {
104 | "title": "index of",
105 | "content": [
106 | "intitle:\"index.of.\"",
107 | "allintitle:\"index of\"",
108 | "\"index of /pub\"",
109 | "parent directory"
110 | ],
111 | "hidden": false
112 | }
113 | }
--------------------------------------------------------------------------------
/popup.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', async () => {
2 |
3 | const searchTermInput = document.querySelector('#searchTermInput');
4 | const searchButton = document.querySelector('#searchButton');
5 | const searchTypeSelect = document.querySelector('#searchTypeSelect');
6 | const searchTimeSelect = document.querySelector('#searchTimeSelect');
7 | const searchEngineSelect = document.querySelector('#searchEngineSelect');
8 | const copyButton = document.querySelector('#copyButton');
9 | const excludeWordsInput = document.querySelector('#excludeWordsInput');
10 | const excludeSitesInput = document.querySelector('#excludeSitesInput');
11 |
12 | const searchEngines = {
13 | 'Google': 'google.com/search?q=',
14 | 'DuckDuckGo': 'duckduckgo.com/?q=',
15 | 'DuckDuckGo (nojs)': 'duckduckgo.com/html?q=',
16 | 'Startpage': 'startpage.com/do/search?query=',
17 | 'Searx': 'searx.me/?q=',
18 | 'metager': 'metager.de/meta/meta.ger3?eingabe='
19 | };
20 |
21 | searchEngineSelect.innerHTML = String(await generateSearchEngineOptionsHTML());
22 |
23 | const presets = await loadPresets();
24 |
25 | searchTypeSelect.innerHTML = String(generateSearchTypeOptionsHTML());
26 |
27 | searchButton.addEventListener('click', search);
28 | copyButton.addEventListener('click', copyQuery);
29 |
30 | document.addEventListener('keyup', e => {
31 | if (e.which === 13) search();
32 | });
33 |
34 | async function search() {
35 | if (!searchTermInput.value) return;
36 |
37 | const query = await buildQuery();
38 |
39 | const timespan = getTimeParameter(searchTimeSelect.value);
40 |
41 | const searchEngine = searchEngines[searchEngineSelect.value];
42 |
43 | const url = buildURL(searchEngine, query, timespan);
44 |
45 | browser.tabs.create({
46 | url,
47 | active: true
48 | });
49 | }
50 |
51 | async function copyQuery () {
52 | if (!searchTermInput.value) return;
53 |
54 | const query = await buildQuery();
55 | await navigator.clipboard.writeText(query);
56 | }
57 |
58 | async function buildQuery () {
59 | const presetName = searchTypeSelect.value;
60 | const includeNames = getPresetIncludes(presetName);
61 | const includes = getContentFromIncludes(includeNames);
62 |
63 | const { prepend = '', append = '' } = presets[presetName].searchTerm || {};
64 | const searchTerms = searchTermInput.value.split(' ').map(term => `${prepend}${term.trim()}${append}`).join('.');
65 |
66 | const excludeWords = makeParamWithOrList('-insite', excludeWordsInput.value);
67 | const excludeSites = makeParamWithOrList('-inurl', excludeSitesInput.value);
68 |
69 | await saveSearchEngine(searchEngineSelect.value);
70 |
71 | return `${searchTerms} ${includes} ${excludeWords} ${excludeSites}`;
72 | }
73 |
74 | function buildURL (searchEngine, query, timespan) {
75 | return `https://${searchEngine}${encodeURIComponent(query)}${timespan}`;
76 | }
77 |
78 | async function loadPresets () {
79 | let { presets } = await browser.storage.sync.get('presets');
80 |
81 | if (true || !presets) {
82 | const data = await fetch('/presets.json');
83 | presets = await data.json();
84 |
85 | await browser.storage.sync.set({ presets });
86 | }
87 | return presets;
88 | }
89 |
90 | function getPresetIncludes(original) {
91 | if (!presets[original]) return [];
92 | return [].concat(presets[original].include || []).reduce(
93 | (r, name) => [...r, ...getPresetIncludes(name)],
94 | [original]
95 | );
96 | }
97 |
98 | function getContentFromIncludes (includes) {
99 | const contents = includes.map(incName => {
100 | return presets[incName].content ? presets[incName].content.join(' ') : '';
101 | });
102 | return contents.join(' ');
103 | }
104 |
105 | function makeParamWithOrList (name, values) {
106 | if (!values.length) return '';
107 |
108 | values = values.replace(/\s*,\s*/g, '|');
109 | return `${name}:(${values})`;
110 | }
111 |
112 | function getTimeParameter (value = '') {
113 | return `&tbs=qdr:${value}`;
114 | }
115 |
116 | function generateSearchTypeOptionsHTML () {
117 | const presetNames = Object.keys(presets);
118 | const options = presetNames.map(key => {
119 | const { hidden, title } = presets[key];
120 | if (!hidden) return ``;
121 | });
122 |
123 | return options.join(' ');
124 | }
125 |
126 | async function generateSearchEngineOptionsHTML () {
127 | const { searchEngine } = await browser.storage.sync.get('searchEngine');
128 |
129 | const searchEngineNames = Object.keys(searchEngines);
130 | const options = searchEngineNames.map(engine => {
131 | const selected = engine === searchEngine ? 'selected' : '';
132 | return ``;
133 | });
134 |
135 | return options.join('');
136 | }
137 |
138 | async function saveSearchEngine (searchEngine) {
139 | await browser.storage.sync.set({ searchEngine });
140 | }
141 | });
--------------------------------------------------------------------------------
/lib/browser-polyfill.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | if (typeof define === "function" && define.amd) {
3 | define("webextension-polyfill", ["module"], factory);
4 | } else if (typeof exports !== "undefined") {
5 | factory(module);
6 | } else {
7 | var mod = {
8 | exports: {}
9 | };
10 | factory(mod);
11 | global.browser = mod.exports;
12 | }
13 | })(this, function (module) {
14 | /* webextension-polyfill - v0.4.0 - Wed Feb 06 2019 11:58:31 */
15 | /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
16 | /* vim: set sts=2 sw=2 et tw=80: */
17 | /* This Source Code Form is subject to the terms of the Mozilla Public
18 | * License, v. 2.0. If a copy of the MPL was not distributed with this
19 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
20 | "use strict";
21 |
22 | if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.prototype) {
23 | const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received.";
24 | const SEND_RESPONSE_DEPRECATION_WARNING = "Returning a Promise is the preferred way to send a reply from an onMessage/onMessageExternal listener, as the sendResponse will be removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)";
25 |
26 | // Wrapping the bulk of this polyfill in a one-time-use function is a minor
27 | // optimization for Firefox. Since Spidermonkey does not fully parse the
28 | // contents of a function until the first time it's called, and since it will
29 | // never actually need to be called, this allows the polyfill to be included
30 | // in Firefox nearly for free.
31 | const wrapAPIs = extensionAPIs => {
32 | // NOTE: apiMetadata is associated to the content of the api-metadata.json file
33 | // at build time by replacing the following "include" with the content of the
34 | // JSON file.
35 | const apiMetadata = {
36 | "alarms": {
37 | "clear": {
38 | "minArgs": 0,
39 | "maxArgs": 1
40 | },
41 | "clearAll": {
42 | "minArgs": 0,
43 | "maxArgs": 0
44 | },
45 | "get": {
46 | "minArgs": 0,
47 | "maxArgs": 1
48 | },
49 | "getAll": {
50 | "minArgs": 0,
51 | "maxArgs": 0
52 | }
53 | },
54 | "bookmarks": {
55 | "create": {
56 | "minArgs": 1,
57 | "maxArgs": 1
58 | },
59 | "get": {
60 | "minArgs": 1,
61 | "maxArgs": 1
62 | },
63 | "getChildren": {
64 | "minArgs": 1,
65 | "maxArgs": 1
66 | },
67 | "getRecent": {
68 | "minArgs": 1,
69 | "maxArgs": 1
70 | },
71 | "getSubTree": {
72 | "minArgs": 1,
73 | "maxArgs": 1
74 | },
75 | "getTree": {
76 | "minArgs": 0,
77 | "maxArgs": 0
78 | },
79 | "move": {
80 | "minArgs": 2,
81 | "maxArgs": 2
82 | },
83 | "remove": {
84 | "minArgs": 1,
85 | "maxArgs": 1
86 | },
87 | "removeTree": {
88 | "minArgs": 1,
89 | "maxArgs": 1
90 | },
91 | "search": {
92 | "minArgs": 1,
93 | "maxArgs": 1
94 | },
95 | "update": {
96 | "minArgs": 2,
97 | "maxArgs": 2
98 | }
99 | },
100 | "browserAction": {
101 | "disable": {
102 | "minArgs": 0,
103 | "maxArgs": 1,
104 | "fallbackToNoCallback": true
105 | },
106 | "enable": {
107 | "minArgs": 0,
108 | "maxArgs": 1,
109 | "fallbackToNoCallback": true
110 | },
111 | "getBadgeBackgroundColor": {
112 | "minArgs": 1,
113 | "maxArgs": 1
114 | },
115 | "getBadgeText": {
116 | "minArgs": 1,
117 | "maxArgs": 1
118 | },
119 | "getPopup": {
120 | "minArgs": 1,
121 | "maxArgs": 1
122 | },
123 | "getTitle": {
124 | "minArgs": 1,
125 | "maxArgs": 1
126 | },
127 | "openPopup": {
128 | "minArgs": 0,
129 | "maxArgs": 0
130 | },
131 | "setBadgeBackgroundColor": {
132 | "minArgs": 1,
133 | "maxArgs": 1,
134 | "fallbackToNoCallback": true
135 | },
136 | "setBadgeText": {
137 | "minArgs": 1,
138 | "maxArgs": 1,
139 | "fallbackToNoCallback": true
140 | },
141 | "setIcon": {
142 | "minArgs": 1,
143 | "maxArgs": 1
144 | },
145 | "setPopup": {
146 | "minArgs": 1,
147 | "maxArgs": 1,
148 | "fallbackToNoCallback": true
149 | },
150 | "setTitle": {
151 | "minArgs": 1,
152 | "maxArgs": 1,
153 | "fallbackToNoCallback": true
154 | }
155 | },
156 | "browsingData": {
157 | "remove": {
158 | "minArgs": 2,
159 | "maxArgs": 2
160 | },
161 | "removeCache": {
162 | "minArgs": 1,
163 | "maxArgs": 1
164 | },
165 | "removeCookies": {
166 | "minArgs": 1,
167 | "maxArgs": 1
168 | },
169 | "removeDownloads": {
170 | "minArgs": 1,
171 | "maxArgs": 1
172 | },
173 | "removeFormData": {
174 | "minArgs": 1,
175 | "maxArgs": 1
176 | },
177 | "removeHistory": {
178 | "minArgs": 1,
179 | "maxArgs": 1
180 | },
181 | "removeLocalStorage": {
182 | "minArgs": 1,
183 | "maxArgs": 1
184 | },
185 | "removePasswords": {
186 | "minArgs": 1,
187 | "maxArgs": 1
188 | },
189 | "removePluginData": {
190 | "minArgs": 1,
191 | "maxArgs": 1
192 | },
193 | "settings": {
194 | "minArgs": 0,
195 | "maxArgs": 0
196 | }
197 | },
198 | "commands": {
199 | "getAll": {
200 | "minArgs": 0,
201 | "maxArgs": 0
202 | }
203 | },
204 | "contextMenus": {
205 | "remove": {
206 | "minArgs": 1,
207 | "maxArgs": 1
208 | },
209 | "removeAll": {
210 | "minArgs": 0,
211 | "maxArgs": 0
212 | },
213 | "update": {
214 | "minArgs": 2,
215 | "maxArgs": 2
216 | }
217 | },
218 | "cookies": {
219 | "get": {
220 | "minArgs": 1,
221 | "maxArgs": 1
222 | },
223 | "getAll": {
224 | "minArgs": 1,
225 | "maxArgs": 1
226 | },
227 | "getAllCookieStores": {
228 | "minArgs": 0,
229 | "maxArgs": 0
230 | },
231 | "remove": {
232 | "minArgs": 1,
233 | "maxArgs": 1
234 | },
235 | "set": {
236 | "minArgs": 1,
237 | "maxArgs": 1
238 | }
239 | },
240 | "devtools": {
241 | "inspectedWindow": {
242 | "eval": {
243 | "minArgs": 1,
244 | "maxArgs": 2,
245 | "singleCallbackArg": false
246 | }
247 | },
248 | "panels": {
249 | "create": {
250 | "minArgs": 3,
251 | "maxArgs": 3,
252 | "singleCallbackArg": true
253 | }
254 | }
255 | },
256 | "downloads": {
257 | "cancel": {
258 | "minArgs": 1,
259 | "maxArgs": 1
260 | },
261 | "download": {
262 | "minArgs": 1,
263 | "maxArgs": 1
264 | },
265 | "erase": {
266 | "minArgs": 1,
267 | "maxArgs": 1
268 | },
269 | "getFileIcon": {
270 | "minArgs": 1,
271 | "maxArgs": 2
272 | },
273 | "open": {
274 | "minArgs": 1,
275 | "maxArgs": 1,
276 | "fallbackToNoCallback": true
277 | },
278 | "pause": {
279 | "minArgs": 1,
280 | "maxArgs": 1
281 | },
282 | "removeFile": {
283 | "minArgs": 1,
284 | "maxArgs": 1
285 | },
286 | "resume": {
287 | "minArgs": 1,
288 | "maxArgs": 1
289 | },
290 | "search": {
291 | "minArgs": 1,
292 | "maxArgs": 1
293 | },
294 | "show": {
295 | "minArgs": 1,
296 | "maxArgs": 1,
297 | "fallbackToNoCallback": true
298 | }
299 | },
300 | "extension": {
301 | "isAllowedFileSchemeAccess": {
302 | "minArgs": 0,
303 | "maxArgs": 0
304 | },
305 | "isAllowedIncognitoAccess": {
306 | "minArgs": 0,
307 | "maxArgs": 0
308 | }
309 | },
310 | "history": {
311 | "addUrl": {
312 | "minArgs": 1,
313 | "maxArgs": 1
314 | },
315 | "deleteAll": {
316 | "minArgs": 0,
317 | "maxArgs": 0
318 | },
319 | "deleteRange": {
320 | "minArgs": 1,
321 | "maxArgs": 1
322 | },
323 | "deleteUrl": {
324 | "minArgs": 1,
325 | "maxArgs": 1
326 | },
327 | "getVisits": {
328 | "minArgs": 1,
329 | "maxArgs": 1
330 | },
331 | "search": {
332 | "minArgs": 1,
333 | "maxArgs": 1
334 | }
335 | },
336 | "i18n": {
337 | "detectLanguage": {
338 | "minArgs": 1,
339 | "maxArgs": 1
340 | },
341 | "getAcceptLanguages": {
342 | "minArgs": 0,
343 | "maxArgs": 0
344 | }
345 | },
346 | "identity": {
347 | "launchWebAuthFlow": {
348 | "minArgs": 1,
349 | "maxArgs": 1
350 | }
351 | },
352 | "idle": {
353 | "queryState": {
354 | "minArgs": 1,
355 | "maxArgs": 1
356 | }
357 | },
358 | "management": {
359 | "get": {
360 | "minArgs": 1,
361 | "maxArgs": 1
362 | },
363 | "getAll": {
364 | "minArgs": 0,
365 | "maxArgs": 0
366 | },
367 | "getSelf": {
368 | "minArgs": 0,
369 | "maxArgs": 0
370 | },
371 | "setEnabled": {
372 | "minArgs": 2,
373 | "maxArgs": 2
374 | },
375 | "uninstallSelf": {
376 | "minArgs": 0,
377 | "maxArgs": 1
378 | }
379 | },
380 | "notifications": {
381 | "clear": {
382 | "minArgs": 1,
383 | "maxArgs": 1
384 | },
385 | "create": {
386 | "minArgs": 1,
387 | "maxArgs": 2
388 | },
389 | "getAll": {
390 | "minArgs": 0,
391 | "maxArgs": 0
392 | },
393 | "getPermissionLevel": {
394 | "minArgs": 0,
395 | "maxArgs": 0
396 | },
397 | "update": {
398 | "minArgs": 2,
399 | "maxArgs": 2
400 | }
401 | },
402 | "pageAction": {
403 | "getPopup": {
404 | "minArgs": 1,
405 | "maxArgs": 1
406 | },
407 | "getTitle": {
408 | "minArgs": 1,
409 | "maxArgs": 1
410 | },
411 | "hide": {
412 | "minArgs": 1,
413 | "maxArgs": 1,
414 | "fallbackToNoCallback": true
415 | },
416 | "setIcon": {
417 | "minArgs": 1,
418 | "maxArgs": 1
419 | },
420 | "setPopup": {
421 | "minArgs": 1,
422 | "maxArgs": 1,
423 | "fallbackToNoCallback": true
424 | },
425 | "setTitle": {
426 | "minArgs": 1,
427 | "maxArgs": 1,
428 | "fallbackToNoCallback": true
429 | },
430 | "show": {
431 | "minArgs": 1,
432 | "maxArgs": 1,
433 | "fallbackToNoCallback": true
434 | }
435 | },
436 | "permissions": {
437 | "contains": {
438 | "minArgs": 1,
439 | "maxArgs": 1
440 | },
441 | "getAll": {
442 | "minArgs": 0,
443 | "maxArgs": 0
444 | },
445 | "remove": {
446 | "minArgs": 1,
447 | "maxArgs": 1
448 | },
449 | "request": {
450 | "minArgs": 1,
451 | "maxArgs": 1
452 | }
453 | },
454 | "runtime": {
455 | "getBackgroundPage": {
456 | "minArgs": 0,
457 | "maxArgs": 0
458 | },
459 | "getBrowserInfo": {
460 | "minArgs": 0,
461 | "maxArgs": 0
462 | },
463 | "getPlatformInfo": {
464 | "minArgs": 0,
465 | "maxArgs": 0
466 | },
467 | "openOptionsPage": {
468 | "minArgs": 0,
469 | "maxArgs": 0
470 | },
471 | "requestUpdateCheck": {
472 | "minArgs": 0,
473 | "maxArgs": 0
474 | },
475 | "sendMessage": {
476 | "minArgs": 1,
477 | "maxArgs": 3
478 | },
479 | "sendNativeMessage": {
480 | "minArgs": 2,
481 | "maxArgs": 2
482 | },
483 | "setUninstallURL": {
484 | "minArgs": 1,
485 | "maxArgs": 1
486 | }
487 | },
488 | "sessions": {
489 | "getDevices": {
490 | "minArgs": 0,
491 | "maxArgs": 1
492 | },
493 | "getRecentlyClosed": {
494 | "minArgs": 0,
495 | "maxArgs": 1
496 | },
497 | "restore": {
498 | "minArgs": 0,
499 | "maxArgs": 1
500 | }
501 | },
502 | "storage": {
503 | "local": {
504 | "clear": {
505 | "minArgs": 0,
506 | "maxArgs": 0
507 | },
508 | "get": {
509 | "minArgs": 0,
510 | "maxArgs": 1
511 | },
512 | "getBytesInUse": {
513 | "minArgs": 0,
514 | "maxArgs": 1
515 | },
516 | "remove": {
517 | "minArgs": 1,
518 | "maxArgs": 1
519 | },
520 | "set": {
521 | "minArgs": 1,
522 | "maxArgs": 1
523 | }
524 | },
525 | "managed": {
526 | "get": {
527 | "minArgs": 0,
528 | "maxArgs": 1
529 | },
530 | "getBytesInUse": {
531 | "minArgs": 0,
532 | "maxArgs": 1
533 | }
534 | },
535 | "sync": {
536 | "clear": {
537 | "minArgs": 0,
538 | "maxArgs": 0
539 | },
540 | "get": {
541 | "minArgs": 0,
542 | "maxArgs": 1
543 | },
544 | "getBytesInUse": {
545 | "minArgs": 0,
546 | "maxArgs": 1
547 | },
548 | "remove": {
549 | "minArgs": 1,
550 | "maxArgs": 1
551 | },
552 | "set": {
553 | "minArgs": 1,
554 | "maxArgs": 1
555 | }
556 | }
557 | },
558 | "tabs": {
559 | "captureVisibleTab": {
560 | "minArgs": 0,
561 | "maxArgs": 2
562 | },
563 | "create": {
564 | "minArgs": 1,
565 | "maxArgs": 1
566 | },
567 | "detectLanguage": {
568 | "minArgs": 0,
569 | "maxArgs": 1
570 | },
571 | "discard": {
572 | "minArgs": 0,
573 | "maxArgs": 1
574 | },
575 | "duplicate": {
576 | "minArgs": 1,
577 | "maxArgs": 1
578 | },
579 | "executeScript": {
580 | "minArgs": 1,
581 | "maxArgs": 2
582 | },
583 | "get": {
584 | "minArgs": 1,
585 | "maxArgs": 1
586 | },
587 | "getCurrent": {
588 | "minArgs": 0,
589 | "maxArgs": 0
590 | },
591 | "getZoom": {
592 | "minArgs": 0,
593 | "maxArgs": 1
594 | },
595 | "getZoomSettings": {
596 | "minArgs": 0,
597 | "maxArgs": 1
598 | },
599 | "highlight": {
600 | "minArgs": 1,
601 | "maxArgs": 1
602 | },
603 | "insertCSS": {
604 | "minArgs": 1,
605 | "maxArgs": 2
606 | },
607 | "move": {
608 | "minArgs": 2,
609 | "maxArgs": 2
610 | },
611 | "query": {
612 | "minArgs": 1,
613 | "maxArgs": 1
614 | },
615 | "reload": {
616 | "minArgs": 0,
617 | "maxArgs": 2
618 | },
619 | "remove": {
620 | "minArgs": 1,
621 | "maxArgs": 1
622 | },
623 | "removeCSS": {
624 | "minArgs": 1,
625 | "maxArgs": 2
626 | },
627 | "sendMessage": {
628 | "minArgs": 2,
629 | "maxArgs": 3
630 | },
631 | "setZoom": {
632 | "minArgs": 1,
633 | "maxArgs": 2
634 | },
635 | "setZoomSettings": {
636 | "minArgs": 1,
637 | "maxArgs": 2
638 | },
639 | "update": {
640 | "minArgs": 1,
641 | "maxArgs": 2
642 | }
643 | },
644 | "topSites": {
645 | "get": {
646 | "minArgs": 0,
647 | "maxArgs": 0
648 | }
649 | },
650 | "webNavigation": {
651 | "getAllFrames": {
652 | "minArgs": 1,
653 | "maxArgs": 1
654 | },
655 | "getFrame": {
656 | "minArgs": 1,
657 | "maxArgs": 1
658 | }
659 | },
660 | "webRequest": {
661 | "handlerBehaviorChanged": {
662 | "minArgs": 0,
663 | "maxArgs": 0
664 | }
665 | },
666 | "windows": {
667 | "create": {
668 | "minArgs": 0,
669 | "maxArgs": 1
670 | },
671 | "get": {
672 | "minArgs": 1,
673 | "maxArgs": 2
674 | },
675 | "getAll": {
676 | "minArgs": 0,
677 | "maxArgs": 1
678 | },
679 | "getCurrent": {
680 | "minArgs": 0,
681 | "maxArgs": 1
682 | },
683 | "getLastFocused": {
684 | "minArgs": 0,
685 | "maxArgs": 1
686 | },
687 | "remove": {
688 | "minArgs": 1,
689 | "maxArgs": 1
690 | },
691 | "update": {
692 | "minArgs": 2,
693 | "maxArgs": 2
694 | }
695 | }
696 | };
697 |
698 | if (Object.keys(apiMetadata).length === 0) {
699 | throw new Error("api-metadata.json has not been included in browser-polyfill");
700 | }
701 |
702 | /**
703 | * A WeakMap subclass which creates and stores a value for any key which does
704 | * not exist when accessed, but behaves exactly as an ordinary WeakMap
705 | * otherwise.
706 | *
707 | * @param {function} createItem
708 | * A function which will be called in order to create the value for any
709 | * key which does not exist, the first time it is accessed. The
710 | * function receives, as its only argument, the key being created.
711 | */
712 | class DefaultWeakMap extends WeakMap {
713 | constructor(createItem, items = undefined) {
714 | super(items);
715 | this.createItem = createItem;
716 | }
717 |
718 | get(key) {
719 | if (!this.has(key)) {
720 | this.set(key, this.createItem(key));
721 | }
722 |
723 | return super.get(key);
724 | }
725 | }
726 |
727 | /**
728 | * Returns true if the given object is an object with a `then` method, and can
729 | * therefore be assumed to behave as a Promise.
730 | *
731 | * @param {*} value The value to test.
732 | * @returns {boolean} True if the value is thenable.
733 | */
734 | const isThenable = value => {
735 | return value && typeof value === "object" && typeof value.then === "function";
736 | };
737 |
738 | /**
739 | * Creates and returns a function which, when called, will resolve or reject
740 | * the given promise based on how it is called:
741 | *
742 | * - If, when called, `chrome.runtime.lastError` contains a non-null object,
743 | * the promise is rejected with that value.
744 | * - If the function is called with exactly one argument, the promise is
745 | * resolved to that value.
746 | * - Otherwise, the promise is resolved to an array containing all of the
747 | * function's arguments.
748 | *
749 | * @param {object} promise
750 | * An object containing the resolution and rejection functions of a
751 | * promise.
752 | * @param {function} promise.resolve
753 | * The promise's resolution function.
754 | * @param {function} promise.rejection
755 | * The promise's rejection function.
756 | * @param {object} metadata
757 | * Metadata about the wrapped method which has created the callback.
758 | * @param {integer} metadata.maxResolvedArgs
759 | * The maximum number of arguments which may be passed to the
760 | * callback created by the wrapped async function.
761 | *
762 | * @returns {function}
763 | * The generated callback function.
764 | */
765 | const makeCallback = (promise, metadata) => {
766 | return (...callbackArgs) => {
767 | if (extensionAPIs.runtime.lastError) {
768 | promise.reject(extensionAPIs.runtime.lastError);
769 | } else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) {
770 | promise.resolve(callbackArgs[0]);
771 | } else {
772 | promise.resolve(callbackArgs);
773 | }
774 | };
775 | };
776 |
777 | const pluralizeArguments = numArgs => numArgs == 1 ? "argument" : "arguments";
778 |
779 | /**
780 | * Creates a wrapper function for a method with the given name and metadata.
781 | *
782 | * @param {string} name
783 | * The name of the method which is being wrapped.
784 | * @param {object} metadata
785 | * Metadata about the method being wrapped.
786 | * @param {integer} metadata.minArgs
787 | * The minimum number of arguments which must be passed to the
788 | * function. If called with fewer than this number of arguments, the
789 | * wrapper will raise an exception.
790 | * @param {integer} metadata.maxArgs
791 | * The maximum number of arguments which may be passed to the
792 | * function. If called with more than this number of arguments, the
793 | * wrapper will raise an exception.
794 | * @param {integer} metadata.maxResolvedArgs
795 | * The maximum number of arguments which may be passed to the
796 | * callback created by the wrapped async function.
797 | *
798 | * @returns {function(object, ...*)}
799 | * The generated wrapper function.
800 | */
801 | const wrapAsyncFunction = (name, metadata) => {
802 | return function asyncFunctionWrapper(target, ...args) {
803 | if (args.length < metadata.minArgs) {
804 | throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
805 | }
806 |
807 | if (args.length > metadata.maxArgs) {
808 | throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
809 | }
810 |
811 | return new Promise((resolve, reject) => {
812 | if (metadata.fallbackToNoCallback) {
813 | // This API method has currently no callback on Chrome, but it return a promise on Firefox,
814 | // and so the polyfill will try to call it with a callback first, and it will fallback
815 | // to not passing the callback if the first call fails.
816 | try {
817 | target[name](...args, makeCallback({ resolve, reject }, metadata));
818 | } catch (cbError) {
819 | console.warn(`${name} API method doesn't seem to support the callback parameter, ` + "falling back to call it without a callback: ", cbError);
820 |
821 | target[name](...args);
822 |
823 | // Update the API method metadata, so that the next API calls will not try to
824 | // use the unsupported callback anymore.
825 | metadata.fallbackToNoCallback = false;
826 | metadata.noCallback = true;
827 |
828 | resolve();
829 | }
830 | } else if (metadata.noCallback) {
831 | target[name](...args);
832 | resolve();
833 | } else {
834 | target[name](...args, makeCallback({ resolve, reject }, metadata));
835 | }
836 | });
837 | };
838 | };
839 |
840 | /**
841 | * Wraps an existing method of the target object, so that calls to it are
842 | * intercepted by the given wrapper function. The wrapper function receives,
843 | * as its first argument, the original `target` object, followed by each of
844 | * the arguments passed to the original method.
845 | *
846 | * @param {object} target
847 | * The original target object that the wrapped method belongs to.
848 | * @param {function} method
849 | * The method being wrapped. This is used as the target of the Proxy
850 | * object which is created to wrap the method.
851 | * @param {function} wrapper
852 | * The wrapper function which is called in place of a direct invocation
853 | * of the wrapped method.
854 | *
855 | * @returns {Proxy}
856 | * A Proxy object for the given method, which invokes the given wrapper
857 | * method in its place.
858 | */
859 | const wrapMethod = (target, method, wrapper) => {
860 | return new Proxy(method, {
861 | apply(targetMethod, thisObj, args) {
862 | return wrapper.call(thisObj, target, ...args);
863 | }
864 | });
865 | };
866 |
867 | let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
868 |
869 | /**
870 | * Wraps an object in a Proxy which intercepts and wraps certain methods
871 | * based on the given `wrappers` and `metadata` objects.
872 | *
873 | * @param {object} target
874 | * The target object to wrap.
875 | *
876 | * @param {object} [wrappers = {}]
877 | * An object tree containing wrapper functions for special cases. Any
878 | * function present in this object tree is called in place of the
879 | * method in the same location in the `target` object tree. These
880 | * wrapper methods are invoked as described in {@see wrapMethod}.
881 | *
882 | * @param {object} [metadata = {}]
883 | * An object tree containing metadata used to automatically generate
884 | * Promise-based wrapper functions for asynchronous. Any function in
885 | * the `target` object tree which has a corresponding metadata object
886 | * in the same location in the `metadata` tree is replaced with an
887 | * automatically-generated wrapper function, as described in
888 | * {@see wrapAsyncFunction}
889 | *
890 | * @returns {Proxy