UNPKG

38.4 kBJavaScriptView Raw
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})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (module) {
14 /* webextension-polyfill - v0.12.0 - Tue May 14 2024 18:01:29 */
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 (!(globalThis.chrome && globalThis.chrome.runtime && globalThis.chrome.runtime.id)) {
23 throw new Error("This script should only be loaded in a browser extension.");
24 }
25 if (!(globalThis.browser && globalThis.browser.runtime && globalThis.browser.runtime.id)) {
26 const CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received.";
27
28 // Wrapping the bulk of this polyfill in a one-time-use function is a minor
29 // optimization for Firefox. Since Spidermonkey does not fully parse the
30 // contents of a function until the first time it's called, and since it will
31 // never actually need to be called, this allows the polyfill to be included
32 // in Firefox nearly for free.
33 const wrapAPIs = extensionAPIs => {
34 // NOTE: apiMetadata is associated to the content of the api-metadata.json file
35 // at build time by replacing the following "include" with the content of the
36 // JSON file.
37 const apiMetadata = {
38 "alarms": {
39 "clear": {
40 "minArgs": 0,
41 "maxArgs": 1
42 },
43 "clearAll": {
44 "minArgs": 0,
45 "maxArgs": 0
46 },
47 "get": {
48 "minArgs": 0,
49 "maxArgs": 1
50 },
51 "getAll": {
52 "minArgs": 0,
53 "maxArgs": 0
54 }
55 },
56 "bookmarks": {
57 "create": {
58 "minArgs": 1,
59 "maxArgs": 1
60 },
61 "get": {
62 "minArgs": 1,
63 "maxArgs": 1
64 },
65 "getChildren": {
66 "minArgs": 1,
67 "maxArgs": 1
68 },
69 "getRecent": {
70 "minArgs": 1,
71 "maxArgs": 1
72 },
73 "getSubTree": {
74 "minArgs": 1,
75 "maxArgs": 1
76 },
77 "getTree": {
78 "minArgs": 0,
79 "maxArgs": 0
80 },
81 "move": {
82 "minArgs": 2,
83 "maxArgs": 2
84 },
85 "remove": {
86 "minArgs": 1,
87 "maxArgs": 1
88 },
89 "removeTree": {
90 "minArgs": 1,
91 "maxArgs": 1
92 },
93 "search": {
94 "minArgs": 1,
95 "maxArgs": 1
96 },
97 "update": {
98 "minArgs": 2,
99 "maxArgs": 2
100 }
101 },
102 "browserAction": {
103 "disable": {
104 "minArgs": 0,
105 "maxArgs": 1,
106 "fallbackToNoCallback": true
107 },
108 "enable": {
109 "minArgs": 0,
110 "maxArgs": 1,
111 "fallbackToNoCallback": true
112 },
113 "getBadgeBackgroundColor": {
114 "minArgs": 1,
115 "maxArgs": 1
116 },
117 "getBadgeText": {
118 "minArgs": 1,
119 "maxArgs": 1
120 },
121 "getPopup": {
122 "minArgs": 1,
123 "maxArgs": 1
124 },
125 "getTitle": {
126 "minArgs": 1,
127 "maxArgs": 1
128 },
129 "openPopup": {
130 "minArgs": 0,
131 "maxArgs": 0
132 },
133 "setBadgeBackgroundColor": {
134 "minArgs": 1,
135 "maxArgs": 1,
136 "fallbackToNoCallback": true
137 },
138 "setBadgeText": {
139 "minArgs": 1,
140 "maxArgs": 1,
141 "fallbackToNoCallback": true
142 },
143 "setIcon": {
144 "minArgs": 1,
145 "maxArgs": 1
146 },
147 "setPopup": {
148 "minArgs": 1,
149 "maxArgs": 1,
150 "fallbackToNoCallback": true
151 },
152 "setTitle": {
153 "minArgs": 1,
154 "maxArgs": 1,
155 "fallbackToNoCallback": true
156 }
157 },
158 "browsingData": {
159 "remove": {
160 "minArgs": 2,
161 "maxArgs": 2
162 },
163 "removeCache": {
164 "minArgs": 1,
165 "maxArgs": 1
166 },
167 "removeCookies": {
168 "minArgs": 1,
169 "maxArgs": 1
170 },
171 "removeDownloads": {
172 "minArgs": 1,
173 "maxArgs": 1
174 },
175 "removeFormData": {
176 "minArgs": 1,
177 "maxArgs": 1
178 },
179 "removeHistory": {
180 "minArgs": 1,
181 "maxArgs": 1
182 },
183 "removeLocalStorage": {
184 "minArgs": 1,
185 "maxArgs": 1
186 },
187 "removePasswords": {
188 "minArgs": 1,
189 "maxArgs": 1
190 },
191 "removePluginData": {
192 "minArgs": 1,
193 "maxArgs": 1
194 },
195 "settings": {
196 "minArgs": 0,
197 "maxArgs": 0
198 }
199 },
200 "commands": {
201 "getAll": {
202 "minArgs": 0,
203 "maxArgs": 0
204 }
205 },
206 "contextMenus": {
207 "remove": {
208 "minArgs": 1,
209 "maxArgs": 1
210 },
211 "removeAll": {
212 "minArgs": 0,
213 "maxArgs": 0
214 },
215 "update": {
216 "minArgs": 2,
217 "maxArgs": 2
218 }
219 },
220 "cookies": {
221 "get": {
222 "minArgs": 1,
223 "maxArgs": 1
224 },
225 "getAll": {
226 "minArgs": 1,
227 "maxArgs": 1
228 },
229 "getAllCookieStores": {
230 "minArgs": 0,
231 "maxArgs": 0
232 },
233 "remove": {
234 "minArgs": 1,
235 "maxArgs": 1
236 },
237 "set": {
238 "minArgs": 1,
239 "maxArgs": 1
240 }
241 },
242 "devtools": {
243 "inspectedWindow": {
244 "eval": {
245 "minArgs": 1,
246 "maxArgs": 2,
247 "singleCallbackArg": false
248 }
249 },
250 "panels": {
251 "create": {
252 "minArgs": 3,
253 "maxArgs": 3,
254 "singleCallbackArg": true
255 },
256 "elements": {
257 "createSidebarPane": {
258 "minArgs": 1,
259 "maxArgs": 1
260 }
261 }
262 }
263 },
264 "downloads": {
265 "cancel": {
266 "minArgs": 1,
267 "maxArgs": 1
268 },
269 "download": {
270 "minArgs": 1,
271 "maxArgs": 1
272 },
273 "erase": {
274 "minArgs": 1,
275 "maxArgs": 1
276 },
277 "getFileIcon": {
278 "minArgs": 1,
279 "maxArgs": 2
280 },
281 "open": {
282 "minArgs": 1,
283 "maxArgs": 1,
284 "fallbackToNoCallback": true
285 },
286 "pause": {
287 "minArgs": 1,
288 "maxArgs": 1
289 },
290 "removeFile": {
291 "minArgs": 1,
292 "maxArgs": 1
293 },
294 "resume": {
295 "minArgs": 1,
296 "maxArgs": 1
297 },
298 "search": {
299 "minArgs": 1,
300 "maxArgs": 1
301 },
302 "show": {
303 "minArgs": 1,
304 "maxArgs": 1,
305 "fallbackToNoCallback": true
306 }
307 },
308 "extension": {
309 "isAllowedFileSchemeAccess": {
310 "minArgs": 0,
311 "maxArgs": 0
312 },
313 "isAllowedIncognitoAccess": {
314 "minArgs": 0,
315 "maxArgs": 0
316 }
317 },
318 "history": {
319 "addUrl": {
320 "minArgs": 1,
321 "maxArgs": 1
322 },
323 "deleteAll": {
324 "minArgs": 0,
325 "maxArgs": 0
326 },
327 "deleteRange": {
328 "minArgs": 1,
329 "maxArgs": 1
330 },
331 "deleteUrl": {
332 "minArgs": 1,
333 "maxArgs": 1
334 },
335 "getVisits": {
336 "minArgs": 1,
337 "maxArgs": 1
338 },
339 "search": {
340 "minArgs": 1,
341 "maxArgs": 1
342 }
343 },
344 "i18n": {
345 "detectLanguage": {
346 "minArgs": 1,
347 "maxArgs": 1
348 },
349 "getAcceptLanguages": {
350 "minArgs": 0,
351 "maxArgs": 0
352 }
353 },
354 "identity": {
355 "launchWebAuthFlow": {
356 "minArgs": 1,
357 "maxArgs": 1
358 }
359 },
360 "idle": {
361 "queryState": {
362 "minArgs": 1,
363 "maxArgs": 1
364 }
365 },
366 "management": {
367 "get": {
368 "minArgs": 1,
369 "maxArgs": 1
370 },
371 "getAll": {
372 "minArgs": 0,
373 "maxArgs": 0
374 },
375 "getSelf": {
376 "minArgs": 0,
377 "maxArgs": 0
378 },
379 "setEnabled": {
380 "minArgs": 2,
381 "maxArgs": 2
382 },
383 "uninstallSelf": {
384 "minArgs": 0,
385 "maxArgs": 1
386 }
387 },
388 "notifications": {
389 "clear": {
390 "minArgs": 1,
391 "maxArgs": 1
392 },
393 "create": {
394 "minArgs": 1,
395 "maxArgs": 2
396 },
397 "getAll": {
398 "minArgs": 0,
399 "maxArgs": 0
400 },
401 "getPermissionLevel": {
402 "minArgs": 0,
403 "maxArgs": 0
404 },
405 "update": {
406 "minArgs": 2,
407 "maxArgs": 2
408 }
409 },
410 "pageAction": {
411 "getPopup": {
412 "minArgs": 1,
413 "maxArgs": 1
414 },
415 "getTitle": {
416 "minArgs": 1,
417 "maxArgs": 1
418 },
419 "hide": {
420 "minArgs": 1,
421 "maxArgs": 1,
422 "fallbackToNoCallback": true
423 },
424 "setIcon": {
425 "minArgs": 1,
426 "maxArgs": 1
427 },
428 "setPopup": {
429 "minArgs": 1,
430 "maxArgs": 1,
431 "fallbackToNoCallback": true
432 },
433 "setTitle": {
434 "minArgs": 1,
435 "maxArgs": 1,
436 "fallbackToNoCallback": true
437 },
438 "show": {
439 "minArgs": 1,
440 "maxArgs": 1,
441 "fallbackToNoCallback": true
442 }
443 },
444 "permissions": {
445 "contains": {
446 "minArgs": 1,
447 "maxArgs": 1
448 },
449 "getAll": {
450 "minArgs": 0,
451 "maxArgs": 0
452 },
453 "remove": {
454 "minArgs": 1,
455 "maxArgs": 1
456 },
457 "request": {
458 "minArgs": 1,
459 "maxArgs": 1
460 }
461 },
462 "runtime": {
463 "getBackgroundPage": {
464 "minArgs": 0,
465 "maxArgs": 0
466 },
467 "getPlatformInfo": {
468 "minArgs": 0,
469 "maxArgs": 0
470 },
471 "openOptionsPage": {
472 "minArgs": 0,
473 "maxArgs": 0
474 },
475 "requestUpdateCheck": {
476 "minArgs": 0,
477 "maxArgs": 0
478 },
479 "sendMessage": {
480 "minArgs": 1,
481 "maxArgs": 3
482 },
483 "sendNativeMessage": {
484 "minArgs": 2,
485 "maxArgs": 2
486 },
487 "setUninstallURL": {
488 "minArgs": 1,
489 "maxArgs": 1
490 }
491 },
492 "sessions": {
493 "getDevices": {
494 "minArgs": 0,
495 "maxArgs": 1
496 },
497 "getRecentlyClosed": {
498 "minArgs": 0,
499 "maxArgs": 1
500 },
501 "restore": {
502 "minArgs": 0,
503 "maxArgs": 1
504 }
505 },
506 "storage": {
507 "local": {
508 "clear": {
509 "minArgs": 0,
510 "maxArgs": 0
511 },
512 "get": {
513 "minArgs": 0,
514 "maxArgs": 1
515 },
516 "getBytesInUse": {
517 "minArgs": 0,
518 "maxArgs": 1
519 },
520 "remove": {
521 "minArgs": 1,
522 "maxArgs": 1
523 },
524 "set": {
525 "minArgs": 1,
526 "maxArgs": 1
527 }
528 },
529 "managed": {
530 "get": {
531 "minArgs": 0,
532 "maxArgs": 1
533 },
534 "getBytesInUse": {
535 "minArgs": 0,
536 "maxArgs": 1
537 }
538 },
539 "sync": {
540 "clear": {
541 "minArgs": 0,
542 "maxArgs": 0
543 },
544 "get": {
545 "minArgs": 0,
546 "maxArgs": 1
547 },
548 "getBytesInUse": {
549 "minArgs": 0,
550 "maxArgs": 1
551 },
552 "remove": {
553 "minArgs": 1,
554 "maxArgs": 1
555 },
556 "set": {
557 "minArgs": 1,
558 "maxArgs": 1
559 }
560 }
561 },
562 "tabs": {
563 "captureVisibleTab": {
564 "minArgs": 0,
565 "maxArgs": 2
566 },
567 "create": {
568 "minArgs": 1,
569 "maxArgs": 1
570 },
571 "detectLanguage": {
572 "minArgs": 0,
573 "maxArgs": 1
574 },
575 "discard": {
576 "minArgs": 0,
577 "maxArgs": 1
578 },
579 "duplicate": {
580 "minArgs": 1,
581 "maxArgs": 1
582 },
583 "executeScript": {
584 "minArgs": 1,
585 "maxArgs": 2
586 },
587 "get": {
588 "minArgs": 1,
589 "maxArgs": 1
590 },
591 "getCurrent": {
592 "minArgs": 0,
593 "maxArgs": 0
594 },
595 "getZoom": {
596 "minArgs": 0,
597 "maxArgs": 1
598 },
599 "getZoomSettings": {
600 "minArgs": 0,
601 "maxArgs": 1
602 },
603 "goBack": {
604 "minArgs": 0,
605 "maxArgs": 1
606 },
607 "goForward": {
608 "minArgs": 0,
609 "maxArgs": 1
610 },
611 "highlight": {
612 "minArgs": 1,
613 "maxArgs": 1
614 },
615 "insertCSS": {
616 "minArgs": 1,
617 "maxArgs": 2
618 },
619 "move": {
620 "minArgs": 2,
621 "maxArgs": 2
622 },
623 "query": {
624 "minArgs": 1,
625 "maxArgs": 1
626 },
627 "reload": {
628 "minArgs": 0,
629 "maxArgs": 2
630 },
631 "remove": {
632 "minArgs": 1,
633 "maxArgs": 1
634 },
635 "removeCSS": {
636 "minArgs": 1,
637 "maxArgs": 2
638 },
639 "sendMessage": {
640 "minArgs": 2,
641 "maxArgs": 3
642 },
643 "setZoom": {
644 "minArgs": 1,
645 "maxArgs": 2
646 },
647 "setZoomSettings": {
648 "minArgs": 1,
649 "maxArgs": 2
650 },
651 "update": {
652 "minArgs": 1,
653 "maxArgs": 2
654 }
655 },
656 "topSites": {
657 "get": {
658 "minArgs": 0,
659 "maxArgs": 0
660 }
661 },
662 "webNavigation": {
663 "getAllFrames": {
664 "minArgs": 1,
665 "maxArgs": 1
666 },
667 "getFrame": {
668 "minArgs": 1,
669 "maxArgs": 1
670 }
671 },
672 "webRequest": {
673 "handlerBehaviorChanged": {
674 "minArgs": 0,
675 "maxArgs": 0
676 }
677 },
678 "windows": {
679 "create": {
680 "minArgs": 0,
681 "maxArgs": 1
682 },
683 "get": {
684 "minArgs": 1,
685 "maxArgs": 2
686 },
687 "getAll": {
688 "minArgs": 0,
689 "maxArgs": 1
690 },
691 "getCurrent": {
692 "minArgs": 0,
693 "maxArgs": 1
694 },
695 "getLastFocused": {
696 "minArgs": 0,
697 "maxArgs": 1
698 },
699 "remove": {
700 "minArgs": 1,
701 "maxArgs": 1
702 },
703 "update": {
704 "minArgs": 2,
705 "maxArgs": 2
706 }
707 }
708 };
709 if (Object.keys(apiMetadata).length === 0) {
710 throw new Error("api-metadata.json has not been included in browser-polyfill");
711 }
712
713 /**
714 * A WeakMap subclass which creates and stores a value for any key which does
715 * not exist when accessed, but behaves exactly as an ordinary WeakMap
716 * otherwise.
717 *
718 * @param {function} createItem
719 * A function which will be called in order to create the value for any
720 * key which does not exist, the first time it is accessed. The
721 * function receives, as its only argument, the key being created.
722 */
723 class DefaultWeakMap extends WeakMap {
724 constructor(createItem, items = undefined) {
725 super(items);
726 this.createItem = createItem;
727 }
728 get(key) {
729 if (!this.has(key)) {
730 this.set(key, this.createItem(key));
731 }
732 return super.get(key);
733 }
734 }
735
736 /**
737 * Returns true if the given object is an object with a `then` method, and can
738 * therefore be assumed to behave as a Promise.
739 *
740 * @param {*} value The value to test.
741 * @returns {boolean} True if the value is thenable.
742 */
743 const isThenable = value => {
744 return value && typeof value === "object" && typeof value.then === "function";
745 };
746
747 /**
748 * Creates and returns a function which, when called, will resolve or reject
749 * the given promise based on how it is called:
750 *
751 * - If, when called, `chrome.runtime.lastError` contains a non-null object,
752 * the promise is rejected with that value.
753 * - If the function is called with exactly one argument, the promise is
754 * resolved to that value.
755 * - Otherwise, the promise is resolved to an array containing all of the
756 * function's arguments.
757 *
758 * @param {object} promise
759 * An object containing the resolution and rejection functions of a
760 * promise.
761 * @param {function} promise.resolve
762 * The promise's resolution function.
763 * @param {function} promise.reject
764 * The promise's rejection function.
765 * @param {object} metadata
766 * Metadata about the wrapped method which has created the callback.
767 * @param {boolean} metadata.singleCallbackArg
768 * Whether or not the promise is resolved with only the first
769 * argument of the callback, alternatively an array of all the
770 * callback arguments is resolved. By default, if the callback
771 * function is invoked with only a single argument, that will be
772 * resolved to the promise, while all arguments will be resolved as
773 * an array if multiple are given.
774 *
775 * @returns {function}
776 * The generated callback function.
777 */
778 const makeCallback = (promise, metadata) => {
779 return (...callbackArgs) => {
780 if (extensionAPIs.runtime.lastError) {
781 promise.reject(new Error(extensionAPIs.runtime.lastError.message));
782 } else if (metadata.singleCallbackArg || callbackArgs.length <= 1 && metadata.singleCallbackArg !== false) {
783 promise.resolve(callbackArgs[0]);
784 } else {
785 promise.resolve(callbackArgs);
786 }
787 };
788 };
789 const pluralizeArguments = numArgs => numArgs == 1 ? "argument" : "arguments";
790
791 /**
792 * Creates a wrapper function for a method with the given name and metadata.
793 *
794 * @param {string} name
795 * The name of the method which is being wrapped.
796 * @param {object} metadata
797 * Metadata about the method being wrapped.
798 * @param {integer} metadata.minArgs
799 * The minimum number of arguments which must be passed to the
800 * function. If called with fewer than this number of arguments, the
801 * wrapper will raise an exception.
802 * @param {integer} metadata.maxArgs
803 * The maximum number of arguments which may be passed to the
804 * function. If called with more than this number of arguments, the
805 * wrapper will raise an exception.
806 * @param {boolean} metadata.singleCallbackArg
807 * Whether or not the promise is resolved with only the first
808 * argument of the callback, alternatively an array of all the
809 * callback arguments is resolved. By default, if the callback
810 * function is invoked with only a single argument, that will be
811 * resolved to the promise, while all arguments will be resolved as
812 * an array if multiple are given.
813 *
814 * @returns {function(object, ...*)}
815 * The generated wrapper function.
816 */
817 const wrapAsyncFunction = (name, metadata) => {
818 return function asyncFunctionWrapper(target, ...args) {
819 if (args.length < metadata.minArgs) {
820 throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
821 }
822 if (args.length > metadata.maxArgs) {
823 throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
824 }
825 return new Promise((resolve, reject) => {
826 if (metadata.fallbackToNoCallback) {
827 // This API method has currently no callback on Chrome, but it return a promise on Firefox,
828 // and so the polyfill will try to call it with a callback first, and it will fallback
829 // to not passing the callback if the first call fails.
830 try {
831 target[name](...args, makeCallback({
832 resolve,
833 reject
834 }, metadata));
835 } catch (cbError) {
836 console.warn(`${name} API method doesn't seem to support the callback parameter, ` + "falling back to call it without a callback: ", cbError);
837 target[name](...args);
838
839 // Update the API method metadata, so that the next API calls will not try to
840 // use the unsupported callback anymore.
841 metadata.fallbackToNoCallback = false;
842 metadata.noCallback = true;
843 resolve();
844 }
845 } else if (metadata.noCallback) {
846 target[name](...args);
847 resolve();
848 } else {
849 target[name](...args, makeCallback({
850 resolve,
851 reject
852 }, metadata));
853 }
854 });
855 };
856 };
857
858 /**
859 * Wraps an existing method of the target object, so that calls to it are
860 * intercepted by the given wrapper function. The wrapper function receives,
861 * as its first argument, the original `target` object, followed by each of
862 * the arguments passed to the original method.
863 *
864 * @param {object} target
865 * The original target object that the wrapped method belongs to.
866 * @param {function} method
867 * The method being wrapped. This is used as the target of the Proxy
868 * object which is created to wrap the method.
869 * @param {function} wrapper
870 * The wrapper function which is called in place of a direct invocation
871 * of the wrapped method.
872 *
873 * @returns {Proxy<function>}
874 * A Proxy object for the given method, which invokes the given wrapper
875 * method in its place.
876 */
877 const wrapMethod = (target, method, wrapper) => {
878 return new Proxy(method, {
879 apply(targetMethod, thisObj, args) {
880 return wrapper.call(thisObj, target, ...args);
881 }
882 });
883 };
884 let hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
885
886 /**
887 * Wraps an object in a Proxy which intercepts and wraps certain methods
888 * based on the given `wrappers` and `metadata` objects.
889 *
890 * @param {object} target
891 * The target object to wrap.
892 *
893 * @param {object} [wrappers = {}]
894 * An object tree containing wrapper functions for special cases. Any
895 * function present in this object tree is called in place of the
896 * method in the same location in the `target` object tree. These
897 * wrapper methods are invoked as described in {@see wrapMethod}.
898 *
899 * @param {object} [metadata = {}]
900 * An object tree containing metadata used to automatically generate
901 * Promise-based wrapper functions for asynchronous. Any function in
902 * the `target` object tree which has a corresponding metadata object
903 * in the same location in the `metadata` tree is replaced with an
904 * automatically-generated wrapper function, as described in
905 * {@see wrapAsyncFunction}
906 *
907 * @returns {Proxy<object>}
908 */
909 const wrapObject = (target, wrappers = {}, metadata = {}) => {
910 let cache = Object.create(null);
911 let handlers = {
912 has(proxyTarget, prop) {
913 return prop in target || prop in cache;
914 },
915 get(proxyTarget, prop, receiver) {
916 if (prop in cache) {
917 return cache[prop];
918 }
919 if (!(prop in target)) {
920 return undefined;
921 }
922 let value = target[prop];
923 if (typeof value === "function") {
924 // This is a method on the underlying object. Check if we need to do
925 // any wrapping.
926
927 if (typeof wrappers[prop] === "function") {
928 // We have a special-case wrapper for this method.
929 value = wrapMethod(target, target[prop], wrappers[prop]);
930 } else if (hasOwnProperty(metadata, prop)) {
931 // This is an async method that we have metadata for. Create a
932 // Promise wrapper for it.
933 let wrapper = wrapAsyncFunction(prop, metadata[prop]);
934 value = wrapMethod(target, target[prop], wrapper);
935 } else {
936 // This is a method that we don't know or care about. Return the
937 // original method, bound to the underlying object.
938 value = value.bind(target);
939 }
940 } else if (typeof value === "object" && value !== null && (hasOwnProperty(wrappers, prop) || hasOwnProperty(metadata, prop))) {
941 // This is an object that we need to do some wrapping for the children
942 // of. Create a sub-object wrapper for it with the appropriate child
943 // metadata.
944 value = wrapObject(value, wrappers[prop], metadata[prop]);
945 } else if (hasOwnProperty(metadata, "*")) {
946 // Wrap all properties in * namespace.
947 value = wrapObject(value, wrappers[prop], metadata["*"]);
948 } else {
949 // We don't need to do any wrapping for this property,
950 // so just forward all access to the underlying object.
951 Object.defineProperty(cache, prop, {
952 configurable: true,
953 enumerable: true,
954 get() {
955 return target[prop];
956 },
957 set(value) {
958 target[prop] = value;
959 }
960 });
961 return value;
962 }
963 cache[prop] = value;
964 return value;
965 },
966 set(proxyTarget, prop, value, receiver) {
967 if (prop in cache) {
968 cache[prop] = value;
969 } else {
970 target[prop] = value;
971 }
972 return true;
973 },
974 defineProperty(proxyTarget, prop, desc) {
975 return Reflect.defineProperty(cache, prop, desc);
976 },
977 deleteProperty(proxyTarget, prop) {
978 return Reflect.deleteProperty(cache, prop);
979 }
980 };
981
982 // Per contract of the Proxy API, the "get" proxy handler must return the
983 // original value of the target if that value is declared read-only and
984 // non-configurable. For this reason, we create an object with the
985 // prototype set to `target` instead of using `target` directly.
986 // Otherwise we cannot return a custom object for APIs that
987 // are declared read-only and non-configurable, such as `chrome.devtools`.
988 //
989 // The proxy handlers themselves will still use the original `target`
990 // instead of the `proxyTarget`, so that the methods and properties are
991 // dereferenced via the original targets.
992 let proxyTarget = Object.create(target);
993 return new Proxy(proxyTarget, handlers);
994 };
995
996 /**
997 * Creates a set of wrapper functions for an event object, which handles
998 * wrapping of listener functions that those messages are passed.
999 *
1000 * A single wrapper is created for each listener function, and stored in a
1001 * map. Subsequent calls to `addListener`, `hasListener`, or `removeListener`
1002 * retrieve the original wrapper, so that attempts to remove a
1003 * previously-added listener work as expected.
1004 *
1005 * @param {DefaultWeakMap<function, function>} wrapperMap
1006 * A DefaultWeakMap object which will create the appropriate wrapper
1007 * for a given listener function when one does not exist, and retrieve
1008 * an existing one when it does.
1009 *
1010 * @returns {object}
1011 */
1012 const wrapEvent = wrapperMap => ({
1013 addListener(target, listener, ...args) {
1014 target.addListener(wrapperMap.get(listener), ...args);
1015 },
1016 hasListener(target, listener) {
1017 return target.hasListener(wrapperMap.get(listener));
1018 },
1019 removeListener(target, listener) {
1020 target.removeListener(wrapperMap.get(listener));
1021 }
1022 });
1023 const onRequestFinishedWrappers = new DefaultWeakMap(listener => {
1024 if (typeof listener !== "function") {
1025 return listener;
1026 }
1027
1028 /**
1029 * Wraps an onRequestFinished listener function so that it will return a
1030 * `getContent()` property which returns a `Promise` rather than using a
1031 * callback API.
1032 *
1033 * @param {object} req
1034 * The HAR entry object representing the network request.
1035 */
1036 return function onRequestFinished(req) {
1037 const wrappedReq = wrapObject(req, {} /* wrappers */, {
1038 getContent: {
1039 minArgs: 0,
1040 maxArgs: 0
1041 }
1042 });
1043 listener(wrappedReq);
1044 };
1045 });
1046 const onMessageWrappers = new DefaultWeakMap(listener => {
1047 if (typeof listener !== "function") {
1048 return listener;
1049 }
1050
1051 /**
1052 * Wraps a message listener function so that it may send responses based on
1053 * its return value, rather than by returning a sentinel value and calling a
1054 * callback. If the listener function returns a Promise, the response is
1055 * sent when the promise either resolves or rejects.
1056 *
1057 * @param {*} message
1058 * The message sent by the other end of the channel.
1059 * @param {object} sender
1060 * Details about the sender of the message.
1061 * @param {function(*)} sendResponse
1062 * A callback which, when called with an arbitrary argument, sends
1063 * that value as a response.
1064 * @returns {boolean}
1065 * True if the wrapped listener returned a Promise, which will later
1066 * yield a response. False otherwise.
1067 */
1068 return function onMessage(message, sender, sendResponse) {
1069 let didCallSendResponse = false;
1070 let wrappedSendResponse;
1071 let sendResponsePromise = new Promise(resolve => {
1072 wrappedSendResponse = function (response) {
1073 didCallSendResponse = true;
1074 resolve(response);
1075 };
1076 });
1077 let result;
1078 try {
1079 result = listener(message, sender, wrappedSendResponse);
1080 } catch (err) {
1081 result = Promise.reject(err);
1082 }
1083 const isResultThenable = result !== true && isThenable(result);
1084
1085 // If the listener didn't returned true or a Promise, or called
1086 // wrappedSendResponse synchronously, we can exit earlier
1087 // because there will be no response sent from this listener.
1088 if (result !== true && !isResultThenable && !didCallSendResponse) {
1089 return false;
1090 }
1091
1092 // A small helper to send the message if the promise resolves
1093 // and an error if the promise rejects (a wrapped sendMessage has
1094 // to translate the message into a resolved promise or a rejected
1095 // promise).
1096 const sendPromisedResult = promise => {
1097 promise.then(msg => {
1098 // send the message value.
1099 sendResponse(msg);
1100 }, error => {
1101 // Send a JSON representation of the error if the rejected value
1102 // is an instance of error, or the object itself otherwise.
1103 let message;
1104 if (error && (error instanceof Error || typeof error.message === "string")) {
1105 message = error.message;
1106 } else {
1107 message = "An unexpected error occurred";
1108 }
1109 sendResponse({
1110 __mozWebExtensionPolyfillReject__: true,
1111 message
1112 });
1113 }).catch(err => {
1114 // Print an error on the console if unable to send the response.
1115 console.error("Failed to send onMessage rejected reply", err);
1116 });
1117 };
1118
1119 // If the listener returned a Promise, send the resolved value as a
1120 // result, otherwise wait the promise related to the wrappedSendResponse
1121 // callback to resolve and send it as a response.
1122 if (isResultThenable) {
1123 sendPromisedResult(result);
1124 } else {
1125 sendPromisedResult(sendResponsePromise);
1126 }
1127
1128 // Let Chrome know that the listener is replying.
1129 return true;
1130 };
1131 });
1132 const wrappedSendMessageCallback = ({
1133 reject,
1134 resolve
1135 }, reply) => {
1136 if (extensionAPIs.runtime.lastError) {
1137 // Detect when none of the listeners replied to the sendMessage call and resolve
1138 // the promise to undefined as in Firefox.
1139 // See https://github.com/mozilla/webextension-polyfill/issues/130
1140 if (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) {
1141 resolve();
1142 } else {
1143 reject(new Error(extensionAPIs.runtime.lastError.message));
1144 }
1145 } else if (reply && reply.__mozWebExtensionPolyfillReject__) {
1146 // Convert back the JSON representation of the error into
1147 // an Error instance.
1148 reject(new Error(reply.message));
1149 } else {
1150 resolve(reply);
1151 }
1152 };
1153 const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => {
1154 if (args.length < metadata.minArgs) {
1155 throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
1156 }
1157 if (args.length > metadata.maxArgs) {
1158 throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
1159 }
1160 return new Promise((resolve, reject) => {
1161 const wrappedCb = wrappedSendMessageCallback.bind(null, {
1162 resolve,
1163 reject
1164 });
1165 args.push(wrappedCb);
1166 apiNamespaceObj.sendMessage(...args);
1167 });
1168 };
1169 const staticWrappers = {
1170 devtools: {
1171 network: {
1172 onRequestFinished: wrapEvent(onRequestFinishedWrappers)
1173 }
1174 },
1175 runtime: {
1176 onMessage: wrapEvent(onMessageWrappers),
1177 onMessageExternal: wrapEvent(onMessageWrappers),
1178 sendMessage: wrappedSendMessage.bind(null, "sendMessage", {
1179 minArgs: 1,
1180 maxArgs: 3
1181 })
1182 },
1183 tabs: {
1184 sendMessage: wrappedSendMessage.bind(null, "sendMessage", {
1185 minArgs: 2,
1186 maxArgs: 3
1187 })
1188 }
1189 };
1190 const settingMetadata = {
1191 clear: {
1192 minArgs: 1,
1193 maxArgs: 1
1194 },
1195 get: {
1196 minArgs: 1,
1197 maxArgs: 1
1198 },
1199 set: {
1200 minArgs: 1,
1201 maxArgs: 1
1202 }
1203 };
1204 apiMetadata.privacy = {
1205 network: {
1206 "*": settingMetadata
1207 },
1208 services: {
1209 "*": settingMetadata
1210 },
1211 websites: {
1212 "*": settingMetadata
1213 }
1214 };
1215 return wrapObject(extensionAPIs, staticWrappers, apiMetadata);
1216 };
1217
1218 // The build process adds a UMD wrapper around this file, which makes the
1219 // `module` variable available.
1220 module.exports = wrapAPIs(chrome);
1221 } else {
1222 module.exports = globalThis.browser;
1223 }
1224});
1225//# sourceMappingURL=browser-polyfill.js.map